diff options
author | Michael Opdenacker <michael.opdenacker@bootlin.com> | 2024-05-11 16:31:30 +0530 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2024-05-21 14:23:43 +0100 |
commit | 4cbce9cdf7d22b0b4fe933867f931019540a6663 (patch) | |
tree | 2349c9f8df3f4f020a3f2ff2a25c9336ed62723a | |
parent | 5f99010e41fc26e674d7dc6b6d9d355bc4243542 (diff) | |
download | poky-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-x | bitbake/bin/bitbake-prserv | 17 | ||||
-rw-r--r-- | bitbake/lib/prserv/__init__.py | 90 | ||||
-rw-r--r-- | bitbake/lib/prserv/client.py | 17 | ||||
-rw-r--r-- | bitbake/lib/prserv/db.py | 205 | ||||
-rw-r--r-- | bitbake/lib/prserv/serv.py | 134 |
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 | |||
16 | import prserv | 16 | import prserv |
17 | import prserv.serv | 17 | import prserv.serv |
18 | 18 | ||
19 | VERSION = "1.1.0" | 19 | VERSION = "2.0.0" |
20 | 20 | ||
21 | PRHOST_DEFAULT="0.0.0.0" | 21 | PRHOST_DEFAULT="0.0.0.0" |
22 | PRPORT_DEFAULT=8585 | 22 | PRPORT_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 | |||
10 | import logging | ||
11 | logger = logging.getLogger("BitBake.PRserv") | ||
12 | |||
13 | from bb.asyncrpc.client import parse_address, ADDR_TYPE_UNIX, ADDR_TYPE_WS | ||
14 | |||
15 | def 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 | |||
24 | def 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 | |||
39 | def _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 | |||
60 | def 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 | |||
65 | def 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 | |||
70 | def 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 | |||
83 | async 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 | ||
7 | import logging | 7 | import logging |
8 | import bb.asyncrpc | 8 | import bb.asyncrpc |
9 | from . import create_async_client | ||
9 | 10 | ||
10 | logger = logging.getLogger("BitBake.PRserv") | 11 | logger = 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 | |||
10 | import prserv | 10 | import prserv |
11 | import time | 11 | import time |
12 | 12 | ||
13 | from . import increase_revision, revision_greater, revision_smaller | ||
14 | |||
13 | try: | 15 | try: |
14 | import sqlite3 | 16 | import sqlite3 |
15 | except ImportError: | 17 | except ImportError: |
@@ -32,15 +34,11 @@ if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3): | |||
32 | # | 34 | # |
33 | 35 | ||
34 | class PRTable(object): | 36 | class 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 | ||
323 | class PRData(object): | 335 | class 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 | |||
12 | import prserv | 12 | import prserv |
13 | import prserv.db | 13 | import prserv.db |
14 | import errno | 14 | import errno |
15 | from . import create_async_client, revision_smaller, increase_revision | ||
15 | import bb.asyncrpc | 16 | import bb.asyncrpc |
16 | 17 | ||
17 | logger = logging.getLogger("BitBake.PRserv") | 18 | logger = 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 | ||
119 | class PRServer(bb.asyncrpc.AsyncServer): | 211 | class 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 | ||
149 | class PRServSingleton(object): | 245 | class 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 | ||
236 | def start_daemon(dbfile, host, port, logfile, read_only=False): | 333 | def 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 |