summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Opdenacker <michael.opdenacker@bootlin.com>2024-05-11 16:31:30 +0530
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-05-21 14:23:43 +0100
commit4cbce9cdf7d22b0b4fe933867f931019540a6663 (patch)
tree2349c9f8df3f4f020a3f2ff2a25c9336ed62723a
parent5f99010e41fc26e674d7dc6b6d9d355bc4243542 (diff)
downloadpoky-4cbce9cdf7d22b0b4fe933867f931019540a6663.tar.gz
bitbake: prserv: add "upstream" server support
Introduce a PRSERVER_UPSTREAM variable that makes the local PR server connect to an "upstream" one. This makes it possible to implement local fixes to an upstream package (revision "x", in a way that gives the local update priority (revision "x.y"). Update the calculation of the new revisions to support the case when prior revisions are not integers, but have an "x.y..." format." Set the comments in the handle_get_pr() function in serv.py for details about the calculation of the local revision. This is done by going on supporting the "history" mode that wasn't used so far (revisions can return to a previous historical value), in addition to the default "no history" mode (revisions can never decrease). Rather than storing the history mode in the database table itself (i.e. "PRMAIN_hist" and "PRMAIN_nohist"), the history mode is now passed through the client requests. As a consequence, the table name is now "PRMAIN", which is incompatible with what was generated before, but avoids confusion if we kept the "PRMAIN_nohist" name for both "history" and "no history" modes. Update the server version to "2.0.0". (Bitbake rev: 48857ec3e075791bd73d92747c609a0a4fda0e0c) Signed-off-by: Michael Opdenacker <michael.opdenacker@bootlin.com> Cc: Joshua Watt <JPEWhacker@gmail.com> Cc: Tim Orling <ticotimo@gmail.com> Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rwxr-xr-xbitbake/bin/bitbake-prserv17
-rw-r--r--bitbake/lib/prserv/__init__.py90
-rw-r--r--bitbake/lib/prserv/client.py17
-rw-r--r--bitbake/lib/prserv/db.py205
-rw-r--r--bitbake/lib/prserv/serv.py134
5 files changed, 338 insertions, 125 deletions
diff --git a/bitbake/bin/bitbake-prserv b/bitbake/bin/bitbake-prserv
index 920663a1d8..580e021fda 100755
--- a/bitbake/bin/bitbake-prserv
+++ b/bitbake/bin/bitbake-prserv
@@ -16,7 +16,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib
16import prserv 16import prserv
17import prserv.serv 17import prserv.serv
18 18
19VERSION = "1.1.0" 19VERSION = "2.0.0"
20 20
21PRHOST_DEFAULT="0.0.0.0" 21PRHOST_DEFAULT="0.0.0.0"
22PRPORT_DEFAULT=8585 22PRPORT_DEFAULT=8585
@@ -77,12 +77,25 @@ def main():
77 action="store_true", 77 action="store_true",
78 help="open database in read-only mode", 78 help="open database in read-only mode",
79 ) 79 )
80 parser.add_argument(
81 "-u",
82 "--upstream",
83 default=os.environ.get("PRSERVER_UPSTREAM", None),
84 help="Upstream PR service (host:port)",
85 )
80 86
81 args = parser.parse_args() 87 args = parser.parse_args()
82 init_logger(os.path.abspath(args.log), args.loglevel) 88 init_logger(os.path.abspath(args.log), args.loglevel)
83 89
84 if args.start: 90 if args.start:
85 ret=prserv.serv.start_daemon(args.file, args.host, args.port, os.path.abspath(args.log), args.read_only) 91 ret=prserv.serv.start_daemon(
92 args.file,
93 args.host,
94 args.port,
95 os.path.abspath(args.log),
96 args.read_only,
97 args.upstream
98 )
86 elif args.stop: 99 elif args.stop:
87 ret=prserv.serv.stop_daemon(args.host, args.port) 100 ret=prserv.serv.stop_daemon(args.host, args.port)
88 else: 101 else:
diff --git a/bitbake/lib/prserv/__init__.py b/bitbake/lib/prserv/__init__.py
index 94658b815d..a817b03c1e 100644
--- a/bitbake/lib/prserv/__init__.py
+++ b/bitbake/lib/prserv/__init__.py
@@ -4,4 +4,92 @@
4# SPDX-License-Identifier: GPL-2.0-only 4# SPDX-License-Identifier: GPL-2.0-only
5# 5#
6 6
7__version__ = "1.0.0" 7
8__version__ = "2.0.0"
9
10import logging
11logger = logging.getLogger("BitBake.PRserv")
12
13from bb.asyncrpc.client import parse_address, ADDR_TYPE_UNIX, ADDR_TYPE_WS
14
15def create_server(addr, dbpath, upstream=None, read_only=False):
16 from . import serv
17
18 s = serv.PRServer(dbpath, upstream=upstream, read_only=read_only)
19 host, port = addr.split(":")
20 s.start_tcp_server(host, int(port))
21
22 return s
23
24def increase_revision(ver):
25 """Take a revision string such as "1" or "1.2.3" or even a number and increase its last number
26 This fails if the last number is not an integer"""
27
28 fields=str(ver).split('.')
29 last = fields[-1]
30
31 try:
32 val = int(last)
33 except Exception as e:
34 logger.critical("Unable to increase revision value %s: %s" % (ver, e))
35 raise e
36
37 return ".".join(fields[0:-1] + list(str(val + 1)))
38
39def _revision_greater_or_equal(rev1, rev2):
40 """Compares x.y.z revision numbers, using integer comparison
41 Returns True if rev1 is greater or equal to rev2"""
42
43 fields1 = rev1.split(".")
44 fields2 = rev2.split(".")
45 l1 = len(fields1)
46 l2 = len(fields2)
47
48 for i in range(l1):
49 val1 = int(fields1[i])
50 if i < l2:
51 val2 = int(fields2[i])
52 if val2 < val1:
53 return True
54 elif val2 > val1:
55 return False
56 else:
57 return True
58 return True
59
60def revision_smaller(rev1, rev2):
61 """Compares x.y.z revision numbers, using integer comparison
62 Returns True if rev1 is strictly smaller than rev2"""
63 return not(_revision_greater_or_equal(rev1, rev2))
64
65def revision_greater(rev1, rev2):
66 """Compares x.y.z revision numbers, using integer comparison
67 Returns True if rev1 is strictly greater than rev2"""
68 return _revision_greater_or_equal(rev1, rev2) and (rev1 != rev2)
69
70def create_client(addr):
71 from . import client
72
73 c = client.PRClient()
74
75 try:
76 (typ, a) = parse_address(addr)
77 c.connect_tcp(*a)
78 return c
79 except Exception as e:
80 c.close()
81 raise e
82
83async def create_async_client(addr):
84 from . import client
85
86 c = client.PRAsyncClient()
87
88 try:
89 (typ, a) = parse_address(addr)
90 await c.connect_tcp(*a)
91 return c
92
93 except Exception as e:
94 await c.close()
95 raise e
diff --git a/bitbake/lib/prserv/client.py b/bitbake/lib/prserv/client.py
index 99fc4e0f7f..565c6f3872 100644
--- a/bitbake/lib/prserv/client.py
+++ b/bitbake/lib/prserv/client.py
@@ -6,6 +6,7 @@
6 6
7import logging 7import logging
8import bb.asyncrpc 8import bb.asyncrpc
9from . import create_async_client
9 10
10logger = logging.getLogger("BitBake.PRserv") 11logger = logging.getLogger("BitBake.PRserv")
11 12
@@ -13,16 +14,16 @@ class PRAsyncClient(bb.asyncrpc.AsyncClient):
13 def __init__(self): 14 def __init__(self):
14 super().__init__("PRSERVICE", "1.0", logger) 15 super().__init__("PRSERVICE", "1.0", logger)
15 16
16 async def getPR(self, version, pkgarch, checksum): 17 async def getPR(self, version, pkgarch, checksum, history=False):
17 response = await self.invoke( 18 response = await self.invoke(
18 {"get-pr": {"version": version, "pkgarch": pkgarch, "checksum": checksum}} 19 {"get-pr": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "history": history}}
19 ) 20 )
20 if response: 21 if response:
21 return response["value"] 22 return response["value"]
22 23
23 async def test_pr(self, version, pkgarch, checksum): 24 async def test_pr(self, version, pkgarch, checksum, history=False):
24 response = await self.invoke( 25 response = await self.invoke(
25 {"test-pr": {"version": version, "pkgarch": pkgarch, "checksum": checksum}} 26 {"test-pr": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "history": history}}
26 ) 27 )
27 if response: 28 if response:
28 return response["value"] 29 return response["value"]
@@ -41,16 +42,16 @@ class PRAsyncClient(bb.asyncrpc.AsyncClient):
41 if response: 42 if response:
42 return response["value"] 43 return response["value"]
43 44
44 async def importone(self, version, pkgarch, checksum, value): 45 async def importone(self, version, pkgarch, checksum, value, history=False):
45 response = await self.invoke( 46 response = await self.invoke(
46 {"import-one": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "value": value}} 47 {"import-one": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "value": value, "history": history}}
47 ) 48 )
48 if response: 49 if response:
49 return response["value"] 50 return response["value"]
50 51
51 async def export(self, version, pkgarch, checksum, colinfo): 52 async def export(self, version, pkgarch, checksum, colinfo, history=False):
52 response = await self.invoke( 53 response = await self.invoke(
53 {"export": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "colinfo": colinfo}} 54 {"export": {"version": version, "pkgarch": pkgarch, "checksum": checksum, "colinfo": colinfo, "history": history}}
54 ) 55 )
55 if response: 56 if response:
56 return (response["metainfo"], response["datainfo"]) 57 return (response["metainfo"], response["datainfo"])
diff --git a/bitbake/lib/prserv/db.py b/bitbake/lib/prserv/db.py
index eb41508198..b2520f3158 100644
--- a/bitbake/lib/prserv/db.py
+++ b/bitbake/lib/prserv/db.py
@@ -10,6 +10,8 @@ import errno
10import prserv 10import prserv
11import time 11import time
12 12
13from . import increase_revision, revision_greater, revision_smaller
14
13try: 15try:
14 import sqlite3 16 import sqlite3
15except ImportError: 17except ImportError:
@@ -32,15 +34,11 @@ if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
32# 34#
33 35
34class PRTable(object): 36class PRTable(object):
35 def __init__(self, conn, table, nohist, read_only): 37 def __init__(self, conn, table, read_only):
36 self.conn = conn 38 self.conn = conn
37 self.nohist = nohist
38 self.read_only = read_only 39 self.read_only = read_only
39 self.dirty = False 40 self.dirty = False
40 if nohist: 41 self.table = table
41 self.table = "%s_nohist" % table
42 else:
43 self.table = "%s_hist" % table
44 42
45 if self.read_only: 43 if self.read_only:
46 table_exists = self._execute( 44 table_exists = self._execute(
@@ -53,8 +51,8 @@ class PRTable(object):
53 (version TEXT NOT NULL, \ 51 (version TEXT NOT NULL, \
54 pkgarch TEXT NOT NULL, \ 52 pkgarch TEXT NOT NULL, \
55 checksum TEXT NOT NULL, \ 53 checksum TEXT NOT NULL, \
56 value INTEGER, \ 54 value TEXT, \
57 PRIMARY KEY (version, pkgarch, checksum));" % self.table) 55 PRIMARY KEY (version, pkgarch, checksum, value));" % self.table)
58 56
59 def _execute(self, *query): 57 def _execute(self, *query):
60 """Execute a query, waiting to acquire a lock if necessary""" 58 """Execute a query, waiting to acquire a lock if necessary"""
@@ -68,6 +66,28 @@ class PRTable(object):
68 continue 66 continue
69 raise exc 67 raise exc
70 68
69 def _extremum_value(self, rows, is_max):
70 value = None
71
72 for row in rows:
73 current_value = row[0]
74 if value is None:
75 value = current_value
76 else:
77 if is_max:
78 is_new_extremum = revision_greater(current_value, value)
79 else:
80 is_new_extremum = revision_smaller(current_value, value)
81 if is_new_extremum:
82 value = current_value
83 return value
84
85 def _max_value(self, rows):
86 return self._extremum_value(rows, True)
87
88 def _min_value(self, rows):
89 return self._extremum_value(rows, False)
90
71 def sync(self): 91 def sync(self):
72 if not self.read_only: 92 if not self.read_only:
73 self.conn.commit() 93 self.conn.commit()
@@ -102,101 +122,93 @@ class PRTable(object):
102 else: 122 else:
103 return False 123 return False
104 124
105 def find_value(self, version, pkgarch, checksum): 125
126 def find_package_max_value(self, version, pkgarch):
127 """Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
128
129 data = self._execute("SELECT value FROM %s where version=? AND pkgarch=?;" % (self.table),
130 (version, pkgarch))
131 rows = data.fetchall()
132 value = self._max_value(rows)
133 return value
134
135 def find_value(self, version, pkgarch, checksum, history=False):
106 """Returns the value for the specified checksum if found or None otherwise.""" 136 """Returns the value for the specified checksum if found or None otherwise."""
107 137
108 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 138 if history:
109 (version, pkgarch, checksum)) 139 return self.find_min_value(version, pkgarch, checksum)
110 row=data.fetchone()
111 if row is not None:
112 return row[0]
113 else: 140 else:
114 return None 141 return self.find_max_value(version, pkgarch, checksum)
115 142
116 def find_max_value(self, version, pkgarch):
117 """Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
118 143
119 data = self._execute("SELECT max(value) FROM %s where version=? AND pkgarch=?;" % (self.table), 144 def _find_extremum_value(self, version, pkgarch, checksum, is_max):
145 """Returns the maximum (if is_max is True) or minimum (if is_max is False) value
146 for (version, pkgarch, checksum), or None if not found. Doesn't create a new value"""
147
148 data = self._execute("SELECT value FROM %s where version=? AND pkgarch=? AND checksum=?;" % (self.table),
149 (version, pkgarch, checksum))
150 rows = data.fetchall()
151 return self._extremum_value(rows, is_max)
152
153 def find_max_value(self, version, pkgarch, checksum):
154 return self._find_extremum_value(version, pkgarch, checksum, True)
155
156 def find_min_value(self, version, pkgarch, checksum):
157 return self._find_extremum_value(version, pkgarch, checksum, False)
158
159 def find_new_subvalue(self, version, pkgarch, base):
160 """Take and increase the greatest "<base>.y" value for (version, pkgarch), or return "<base>.0" if not found.
161 This doesn't store a new value."""
162
163 data = self._execute("SELECT value FROM %s where version=? AND pkgarch=? AND value LIKE '%s.%%';" % (self.table, base),
120 (version, pkgarch)) 164 (version, pkgarch))
121 row = data.fetchone() 165 rows = data.fetchall()
122 if row is not None: 166 value = self._max_value(rows)
123 return row[0]
124 else:
125 return None
126 167
127 def _get_value_hist(self, version, pkgarch, checksum): 168 if value is not None:
128 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 169 return increase_revision(value)
129 (version, pkgarch, checksum))
130 row=data.fetchone()
131 if row is not None:
132 return row[0]
133 else: 170 else:
134 #no value found, try to insert 171 return base + ".0"
135 if self.read_only:
136 data = self._execute("SELECT ifnull(max(value)+1, 0) FROM %s where version=? AND pkgarch=?;" % (self.table),
137 (version, pkgarch))
138 row = data.fetchone()
139 if row is not None:
140 return row[0]
141 else:
142 return 0
143 172
144 try: 173 def store_value(self, version, pkgarch, checksum, value):
145 self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1, 0) from %s where version=? AND pkgarch=?));" 174 """Store new value in the database"""
146 % (self.table, self.table),
147 (version, pkgarch, checksum, version, pkgarch))
148 except sqlite3.IntegrityError as exc:
149 logger.error(str(exc))
150 175
151 self.dirty = True 176 try:
177 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
178 (version, pkgarch, checksum, value))
179 except sqlite3.IntegrityError as exc:
180 logger.error(str(exc))
152 181
153 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 182 self.dirty = True
154 (version, pkgarch, checksum))
155 row=data.fetchone()
156 if row is not None:
157 return row[0]
158 else:
159 raise prserv.NotFoundError
160 183
161 def _get_value_no_hist(self, version, pkgarch, checksum): 184 def _get_value(self, version, pkgarch, checksum, history):
162 data=self._execute("SELECT value FROM %s \
163 WHERE version=? AND pkgarch=? AND checksum=? AND \
164 value >= (select max(value) from %s where version=? AND pkgarch=?);"
165 % (self.table, self.table),
166 (version, pkgarch, checksum, version, pkgarch))
167 row=data.fetchone()
168 if row is not None:
169 return row[0]
170 else:
171 #no value found, try to insert
172 if self.read_only:
173 data = self._execute("SELECT ifnull(max(value)+1, 0) FROM %s where version=? AND pkgarch=?;" % (self.table),
174 (version, pkgarch))
175 return data.fetchone()[0]
176 185
177 try: 186 max_value = self.find_package_max_value(version, pkgarch)
178 self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1, 0) from %s where version=? AND pkgarch=?));"
179 % (self.table, self.table),
180 (version, pkgarch, checksum, version, pkgarch))
181 except sqlite3.IntegrityError as exc:
182 logger.error(str(exc))
183 self.conn.rollback()
184 187
185 self.dirty = True 188 if max_value is None:
189 # version, pkgarch completely unknown. Return initial value.
190 return "0"
186 191
187 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 192 value = self.find_value(version, pkgarch, checksum, history)
188 (version, pkgarch, checksum))
189 row=data.fetchone()
190 if row is not None:
191 return row[0]
192 else:
193 raise prserv.NotFoundError
194 193
195 def get_value(self, version, pkgarch, checksum): 194 if value is None:
196 if self.nohist: 195 # version, pkgarch found but not checksum. Create a new value from the maximum one
197 return self._get_value_no_hist(version, pkgarch, checksum) 196 return increase_revision(max_value)
197
198 if history:
199 return value
200
201 # "no history" mode - If the value is not the maximum value for the package, need to increase it.
202 if max_value > value:
203 return increase_revision(max_value)
198 else: 204 else:
199 return self._get_value_hist(version, pkgarch, checksum) 205 return value
206
207 def get_value(self, version, pkgarch, checksum, history):
208 value = self._get_value(version, pkgarch, checksum, history)
209 if not self.read_only:
210 self.store_value(version, pkgarch, checksum, value)
211 return value
200 212
201 def _import_hist(self, version, pkgarch, checksum, value): 213 def _import_hist(self, version, pkgarch, checksum, value):
202 if self.read_only: 214 if self.read_only:
@@ -252,13 +264,13 @@ class PRTable(object):
252 else: 264 else:
253 return None 265 return None
254 266
255 def importone(self, version, pkgarch, checksum, value): 267 def importone(self, version, pkgarch, checksum, value, history=False):
256 if self.nohist: 268 if history:
257 return self._import_no_hist(version, pkgarch, checksum, value)
258 else:
259 return self._import_hist(version, pkgarch, checksum, value) 269 return self._import_hist(version, pkgarch, checksum, value)
270 else:
271 return self._import_no_hist(version, pkgarch, checksum, value)
260 272
261 def export(self, version, pkgarch, checksum, colinfo): 273 def export(self, version, pkgarch, checksum, colinfo, history=False):
262 metainfo = {} 274 metainfo = {}
263 #column info 275 #column info
264 if colinfo: 276 if colinfo:
@@ -278,12 +290,12 @@ class PRTable(object):
278 #data info 290 #data info
279 datainfo = [] 291 datainfo = []
280 292
281 if self.nohist: 293 if history:
294 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
295 else:
282 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \ 296 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
283 (SELECT version, pkgarch, max(value) as maxvalue FROM %s GROUP BY version, pkgarch) as T2 \ 297 (SELECT version, pkgarch, max(value) as maxvalue FROM %s GROUP BY version, pkgarch) as T2 \
284 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table) 298 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
285 else:
286 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
287 sqlarg = [] 299 sqlarg = []
288 where = "" 300 where = ""
289 if version: 301 if version:
@@ -322,9 +334,8 @@ class PRTable(object):
322 334
323class PRData(object): 335class PRData(object):
324 """Object representing the PR database""" 336 """Object representing the PR database"""
325 def __init__(self, filename, nohist=True, read_only=False): 337 def __init__(self, filename, read_only=False):
326 self.filename=os.path.abspath(filename) 338 self.filename=os.path.abspath(filename)
327 self.nohist=nohist
328 self.read_only = read_only 339 self.read_only = read_only
329 #build directory hierarchy 340 #build directory hierarchy
330 try: 341 try:
@@ -351,7 +362,7 @@ class PRData(object):
351 if tblname in self._tables: 362 if tblname in self._tables:
352 return self._tables[tblname] 363 return self._tables[tblname]
353 else: 364 else:
354 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only) 365 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.read_only)
355 return tableobj 366 return tableobj
356 367
357 def __delitem__(self, tblname): 368 def __delitem__(self, tblname):
diff --git a/bitbake/lib/prserv/serv.py b/bitbake/lib/prserv/serv.py
index dc4be5b620..05573d06cc 100644
--- a/bitbake/lib/prserv/serv.py
+++ b/bitbake/lib/prserv/serv.py
@@ -12,6 +12,7 @@ import sqlite3
12import prserv 12import prserv
13import prserv.db 13import prserv.db
14import errno 14import errno
15from . import create_async_client, revision_smaller, increase_revision
15import bb.asyncrpc 16import bb.asyncrpc
16 17
17logger = logging.getLogger("BitBake.PRserv") 18logger = logging.getLogger("BitBake.PRserv")
@@ -51,8 +52,9 @@ class PRServerClient(bb.asyncrpc.AsyncServerConnection):
51 version = request["version"] 52 version = request["version"]
52 pkgarch = request["pkgarch"] 53 pkgarch = request["pkgarch"]
53 checksum = request["checksum"] 54 checksum = request["checksum"]
55 history = request["history"]
54 56
55 value = self.server.table.find_value(version, pkgarch, checksum) 57 value = self.server.table.find_value(version, pkgarch, checksum, history)
56 return {"value": value} 58 return {"value": value}
57 59
58 async def handle_test_package(self, request): 60 async def handle_test_package(self, request):
@@ -68,22 +70,110 @@ class PRServerClient(bb.asyncrpc.AsyncServerConnection):
68 version = request["version"] 70 version = request["version"]
69 pkgarch = request["pkgarch"] 71 pkgarch = request["pkgarch"]
70 72
71 value = self.server.table.find_max_value(version, pkgarch) 73 value = self.server.table.find_package_max_value(version, pkgarch)
72 return {"value": value} 74 return {"value": value}
73 75
74 async def handle_get_pr(self, request): 76 async def handle_get_pr(self, request):
75 version = request["version"] 77 version = request["version"]
76 pkgarch = request["pkgarch"] 78 pkgarch = request["pkgarch"]
77 checksum = request["checksum"] 79 checksum = request["checksum"]
80 history = request["history"]
78 81
79 response = None 82 if self.upstream_client is None:
80 try: 83 value = self.server.table.get_value(version, pkgarch, checksum, history)
81 value = self.server.table.get_value(version, pkgarch, checksum) 84 return {"value": value}
82 response = {"value": value}
83 except prserv.NotFoundError:
84 self.logger.error("failure storing value in database for (%s, %s)",version, checksum)
85 85
86 return response 86 # We have an upstream server.
87 # Check whether the local server already knows the requested configuration.
88 # If the configuration is a new one, the generated value we will add will
89 # depend on what's on the upstream server. That's why we're calling find_value()
90 # instead of get_value() directly.
91
92 value = self.server.table.find_value(version, pkgarch, checksum, history)
93 upstream_max = await self.upstream_client.max_package_pr(version, pkgarch)
94
95 if value is not None:
96
97 # The configuration is already known locally.
98
99 if history:
100 value = self.server.table.get_value(version, pkgarch, checksum, history)
101 else:
102 existing_value = value
103 # In "no history", we need to make sure the value doesn't decrease
104 # and is at least greater than the maximum upstream value
105 # and the maximum local value
106
107 local_max = self.server.table.find_package_max_value(version, pkgarch)
108 if revision_smaller(value, local_max):
109 value = increase_revision(local_max)
110
111 if revision_smaller(value, upstream_max):
112 # Ask upstream whether it knows the checksum
113 upstream_value = await self.upstream_client.test_pr(version, pkgarch, checksum)
114 if upstream_value is None:
115 # Upstream doesn't have our checksum, let create a new one
116 value = upstream_max + ".0"
117 else:
118 # Fine to take the same value as upstream
119 value = upstream_max
120
121 if not value == existing_value and not self.server.read_only:
122 self.server.table.store_value(version, pkgarch, checksum, value)
123
124 return {"value": value}
125
126 # The configuration is a new one for the local server
127 # Let's ask the upstream server whether it knows it
128
129 known_upstream = await self.upstream_client.test_package(version, pkgarch)
130
131 if not known_upstream:
132
133 # The package is not known upstream, must be a local-only package
134 # Let's compute the PR number using the local-only method
135
136 value = self.server.table.get_value(version, pkgarch, checksum, history)
137 return {"value": value}
138
139 # The package is known upstream, let's ask the upstream server
140 # whether it knows our new output hash
141
142 value = await self.upstream_client.test_pr(version, pkgarch, checksum)
143
144 if value is not None:
145
146 # Upstream knows this output hash, let's store it and use it too.
147
148 if not self.server.read_only:
149 self.server.table.store_value(version, pkgarch, checksum, value)
150 # If the local server is read only, won't be able to store the new
151 # value in the database and will have to keep asking the upstream server
152 return {"value": value}
153
154 # The output hash doesn't exist upstream, get the most recent number from upstream (x)
155 # Then, we want to have a new PR value for the local server: x.y
156
157 upstream_max = await self.upstream_client.max_package_pr(version, pkgarch)
158 # Here we know that the package is known upstream, so upstream_max can't be None
159 subvalue = self.server.table.find_new_subvalue(version, pkgarch, upstream_max)
160
161 if not self.server.read_only:
162 self.server.table.store_value(version, pkgarch, checksum, subvalue)
163
164 return {"value": subvalue}
165
166 async def process_requests(self):
167 if self.server.upstream is not None:
168 self.upstream_client = await create_async_client(self.server.upstream)
169 else:
170 self.upstream_client = None
171
172 try:
173 await super().process_requests()
174 finally:
175 if self.upstream_client is not None:
176 await self.upstream_client.close()
87 177
88 async def handle_import_one(self, request): 178 async def handle_import_one(self, request):
89 response = None 179 response = None
@@ -92,8 +182,9 @@ class PRServerClient(bb.asyncrpc.AsyncServerConnection):
92 pkgarch = request["pkgarch"] 182 pkgarch = request["pkgarch"]
93 checksum = request["checksum"] 183 checksum = request["checksum"]
94 value = request["value"] 184 value = request["value"]
185 history = request["history"]
95 186
96 value = self.server.table.importone(version, pkgarch, checksum, value) 187 value = self.server.table.importone(version, pkgarch, checksum, value, history)
97 if value is not None: 188 if value is not None:
98 response = {"value": value} 189 response = {"value": value}
99 190
@@ -104,9 +195,10 @@ class PRServerClient(bb.asyncrpc.AsyncServerConnection):
104 pkgarch = request["pkgarch"] 195 pkgarch = request["pkgarch"]
105 checksum = request["checksum"] 196 checksum = request["checksum"]
106 colinfo = request["colinfo"] 197 colinfo = request["colinfo"]
198 history = request["history"]
107 199
108 try: 200 try:
109 (metainfo, datainfo) = self.server.table.export(version, pkgarch, checksum, colinfo) 201 (metainfo, datainfo) = self.server.table.export(version, pkgarch, checksum, colinfo, history)
110 except sqlite3.Error as exc: 202 except sqlite3.Error as exc:
111 self.logger.error(str(exc)) 203 self.logger.error(str(exc))
112 metainfo = datainfo = None 204 metainfo = datainfo = None
@@ -117,11 +209,12 @@ class PRServerClient(bb.asyncrpc.AsyncServerConnection):
117 return {"readonly": self.server.read_only} 209 return {"readonly": self.server.read_only}
118 210
119class PRServer(bb.asyncrpc.AsyncServer): 211class PRServer(bb.asyncrpc.AsyncServer):
120 def __init__(self, dbfile, read_only=False): 212 def __init__(self, dbfile, read_only=False, upstream=None):
121 super().__init__(logger) 213 super().__init__(logger)
122 self.dbfile = dbfile 214 self.dbfile = dbfile
123 self.table = None 215 self.table = None
124 self.read_only = read_only 216 self.read_only = read_only
217 self.upstream = upstream
125 218
126 def accept_client(self, socket): 219 def accept_client(self, socket):
127 return PRServerClient(socket, self) 220 return PRServerClient(socket, self)
@@ -134,6 +227,9 @@ class PRServer(bb.asyncrpc.AsyncServer):
134 self.logger.info("Started PRServer with DBfile: %s, Address: %s, PID: %s" % 227 self.logger.info("Started PRServer with DBfile: %s, Address: %s, PID: %s" %
135 (self.dbfile, self.address, str(os.getpid()))) 228 (self.dbfile, self.address, str(os.getpid())))
136 229
230 if self.upstream is not None:
231 self.logger.info("And upstream PRServer: %s " % (self.upstream))
232
137 return tasks 233 return tasks
138 234
139 async def stop(self): 235 async def stop(self):
@@ -147,14 +243,15 @@ class PRServer(bb.asyncrpc.AsyncServer):
147 self.table.sync() 243 self.table.sync()
148 244
149class PRServSingleton(object): 245class PRServSingleton(object):
150 def __init__(self, dbfile, logfile, host, port): 246 def __init__(self, dbfile, logfile, host, port, upstream):
151 self.dbfile = dbfile 247 self.dbfile = dbfile
152 self.logfile = logfile 248 self.logfile = logfile
153 self.host = host 249 self.host = host
154 self.port = port 250 self.port = port
251 self.upstream = upstream
155 252
156 def start(self): 253 def start(self):
157 self.prserv = PRServer(self.dbfile) 254 self.prserv = PRServer(self.dbfile, upstream=self.upstream)
158 self.prserv.start_tcp_server(socket.gethostbyname(self.host), self.port) 255 self.prserv.start_tcp_server(socket.gethostbyname(self.host), self.port)
159 self.process = self.prserv.serve_as_process(log_level=logging.WARNING) 256 self.process = self.prserv.serve_as_process(log_level=logging.WARNING)
160 257
@@ -233,7 +330,7 @@ def run_as_daemon(func, pidfile, logfile):
233 os.remove(pidfile) 330 os.remove(pidfile)
234 os._exit(0) 331 os._exit(0)
235 332
236def start_daemon(dbfile, host, port, logfile, read_only=False): 333def start_daemon(dbfile, host, port, logfile, read_only=False, upstream=None):
237 ip = socket.gethostbyname(host) 334 ip = socket.gethostbyname(host)
238 pidfile = PIDPREFIX % (ip, port) 335 pidfile = PIDPREFIX % (ip, port)
239 try: 336 try:
@@ -249,7 +346,7 @@ def start_daemon(dbfile, host, port, logfile, read_only=False):
249 346
250 dbfile = os.path.abspath(dbfile) 347 dbfile = os.path.abspath(dbfile)
251 def daemon_main(): 348 def daemon_main():
252 server = PRServer(dbfile, read_only=read_only) 349 server = PRServer(dbfile, read_only=read_only, upstream=upstream)
253 server.start_tcp_server(ip, port) 350 server.start_tcp_server(ip, port)
254 server.serve_forever() 351 server.serve_forever()
255 352
@@ -336,6 +433,9 @@ def auto_start(d):
336 433
337 host = host_params[0].strip().lower() 434 host = host_params[0].strip().lower()
338 port = int(host_params[1]) 435 port = int(host_params[1])
436
437 upstream = d.getVar("PRSERV_UPSTREAM") or None
438
339 if is_local_special(host, port): 439 if is_local_special(host, port):
340 import bb.utils 440 import bb.utils
341 cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE")) 441 cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE"))
@@ -350,7 +450,7 @@ def auto_start(d):
350 auto_shutdown() 450 auto_shutdown()
351 if not singleton: 451 if not singleton:
352 bb.utils.mkdirhier(cachedir) 452 bb.utils.mkdirhier(cachedir)
353 singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port) 453 singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port, upstream)
354 singleton.start() 454 singleton.start()
355 if singleton: 455 if singleton:
356 host = singleton.host 456 host = singleton.host