Merge lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt into lp:openerp-tools
- trunk-configured-branches-vmt
- Merge into trunk
Proposed by
Vo Minh Thu
Status: | Merged |
---|---|
Merged at revision: | 197 |
Proposed branch: | lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt |
Merge into: | lp:openerp-tools |
Diff against target: |
1776 lines (+684/-606) 13 files modified
openerp-runbot/openerp-runbot (+6/-1) openerp-runbot/openerprunbot/__init__.py (+1/-0) openerp-runbot/openerprunbot/core.py (+154/-578) openerp-runbot/openerprunbot/jobs/__init__.py (+7/-0) openerp-runbot/openerprunbot/jobs/install_all.py (+366/-0) openerp-runbot/openerprunbot/jobs/migrate_script.py (+61/-0) openerp-runbot/openerprunbot/misc.py (+54/-1) openerp-runbot/openerprunbot/templates/branches.html.mako (+2/-1) openerp-runbot/openerprunbot/templates/defs.html.mako (+24/-18) openerp-runbot/openerprunbot/templates/nginx.conf.mako (+1/-1) openerp-runbot/starting.html (+1/-0) openerp-runbot/stopped.html (+1/-0) openerp-runbot/try-template.py (+6/-6) |
To merge this branch: | bzr merge lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP R&D Team | Pending | ||
Review via email: mp+113346@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 201. By OpenERP Online
-
[FIX] runbot: the completing branches were used in place of the original ones.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openerp-runbot/openerp-runbot' | |||
2 | --- openerp-runbot/openerp-runbot 2012-04-19 15:34:05 +0000 | |||
3 | +++ openerp-runbot/openerp-runbot 2012-07-04 11:46:17 +0000 | |||
4 | @@ -57,6 +57,7 @@ | |||
5 | 57 | parser.add_option("--test", metavar="INT", default=1, help="run tests flag (%default)") | 57 | parser.add_option("--test", metavar="INT", default=1, help="run tests flag (%default)") |
6 | 58 | parser.add_option("--workers", metavar="INT", default=4, help="number of workers (%default)") | 58 | parser.add_option("--workers", metavar="INT", default=4, help="number of workers (%default)") |
7 | 59 | parser.add_option("--start-job-id", metavar="INT", default=0, help="initial job id (%default)") | 59 | parser.add_option("--start-job-id", metavar="INT", default=0, help="initial job id (%default)") |
8 | 60 | parser.add_option("--job-type", metavar="JOB_TYPE", default='install_all', help="name of the default job type (%default)") | ||
9 | 60 | parser.add_option("--debug", action="store_true", help="ease debugging by e.g. limiting branches") | 61 | parser.add_option("--debug", action="store_true", help="ease debugging by e.g. limiting branches") |
10 | 61 | o, a = parser.parse_args(sys.argv) | 62 | o, a = parser.parse_args(sys.argv) |
11 | 62 | if o.init: | 63 | if o.init: |
12 | @@ -64,6 +65,9 @@ | |||
13 | 64 | elif o.clean: | 65 | elif o.clean: |
14 | 65 | runbot_clean(o.dir) | 66 | runbot_clean(o.dir) |
15 | 66 | elif o.run: | 67 | elif o.run: |
16 | 68 | assert o.job_type in openerprunbot.jobs.JOBS | ||
17 | 69 | path = os.path.dirname(sys.modules['__main__'].__file__) | ||
18 | 70 | openerprunbot.core.run(["cp", os.path.join(path, "starting.html"), "static/index.html"]) | ||
19 | 67 | openerprunbot.server.read_state() | 71 | openerprunbot.server.read_state() |
20 | 68 | openerprunbot.server.start_server(int(o.nginx_port)-1) | 72 | openerprunbot.server.start_server(int(o.nginx_port)-1) |
21 | 69 | server_net_port = int(o.nginx_port) + int(o.number) * 2 | 73 | server_net_port = int(o.nginx_port) + int(o.number) * 2 |
22 | @@ -76,10 +80,11 @@ | |||
23 | 76 | r = openerprunbot.core.RunBot(o.dir, o.poll, server_net_port, | 80 | r = openerprunbot.core.RunBot(o.dir, o.poll, server_net_port, |
24 | 77 | server_xml_port, client_web_port, o.number, o.nginx_port, | 81 | server_xml_port, client_web_port, o.number, o.nginx_port, |
25 | 78 | o.nginx_domain, o.test, o.workers, int(o.start_job_id), | 82 | o.nginx_domain, o.test, o.workers, int(o.start_job_id), |
27 | 79 | o.debug, lp) | 83 | o.debug, lp, o.job_type) |
28 | 80 | runbot_kill_msg() | 84 | runbot_kill_msg() |
29 | 81 | r.loop() | 85 | r.loop() |
30 | 82 | runbot_kill_msg() | 86 | runbot_kill_msg() |
31 | 87 | openerprunbot.core.run(["cp", os.path.join(path, "stopped.html"), "static/index.html"]) | ||
32 | 83 | print "Last used job id:", r.current_job_id | 88 | print "Last used job id:", r.current_job_id |
33 | 84 | else: | 89 | else: |
34 | 85 | parser.print_help() | 90 | parser.print_help() |
35 | 86 | 91 | ||
36 | === modified file 'openerp-runbot/openerprunbot/__init__.py' | |||
37 | --- openerp-runbot/openerprunbot/__init__.py 2011-12-06 23:17:14 +0000 | |||
38 | +++ openerp-runbot/openerprunbot/__init__.py 2012-07-04 11:46:17 +0000 | |||
39 | @@ -5,6 +5,7 @@ | |||
40 | 5 | import re | 5 | import re |
41 | 6 | 6 | ||
42 | 7 | from . import core | 7 | from . import core |
43 | 8 | from . import jobs | ||
44 | 8 | from . import launchpad | 9 | from . import launchpad |
45 | 9 | from . import misc | 10 | from . import misc |
46 | 10 | from . import server | 11 | from . import server |
47 | 11 | 12 | ||
48 | === modified file 'openerp-runbot/openerprunbot/core.py' | |||
49 | --- openerp-runbot/openerprunbot/core.py 2012-05-07 07:21:32 +0000 | |||
50 | +++ openerp-runbot/openerprunbot/core.py 2012-07-04 11:46:17 +0000 | |||
51 | @@ -16,6 +16,7 @@ | |||
52 | 16 | 16 | ||
53 | 17 | import openerprunbot | 17 | import openerprunbot |
54 | 18 | from openerprunbot.misc import * | 18 | from openerprunbot.misc import * |
55 | 19 | from openerprunbot.jobs.install_all import InstallAllJob | ||
56 | 19 | 20 | ||
57 | 20 | # Hard-coded branches we always want, used to complement monitored branches | 21 | # Hard-coded branches we always want, used to complement monitored branches |
58 | 21 | # (i.e. to create branch groups). | 22 | # (i.e. to create branch groups). |
59 | @@ -31,67 +32,36 @@ | |||
60 | 31 | 'web_trunk': '~openerp/openerp-web/trunk', | 32 | 'web_trunk': '~openerp/openerp-web/trunk', |
61 | 32 | } | 33 | } |
62 | 33 | 34 | ||
63 | 35 | migration_scripts_branch = None | ||
64 | 36 | migration_platform_branch = None | ||
65 | 37 | |||
66 | 34 | # Number of build 'slots' per branch group. | 38 | # Number of build 'slots' per branch group. |
67 | 35 | POINTS = 5 | 39 | POINTS = 5 |
68 | 36 | 40 | ||
69 | 41 | # Job states | ||
70 | 42 | STATE_ALLOCATED = 'allocated' | ||
71 | 43 | STATE_BROKEN = 'broken' | ||
72 | 44 | STATE_PULLING = 'pulling' | ||
73 | 45 | STATE_RUNNING = 'running' | ||
74 | 46 | STATE_TESTING = 'testing' | ||
75 | 47 | |||
76 | 37 | # This constant matches community_dashboard.py. | 48 | # This constant matches community_dashboard.py. |
77 | 38 | NEW_MERGE_STATUS = ['Needs review', 'Code failed to merge', 'Approved'] | 49 | NEW_MERGE_STATUS = ['Needs review', 'Code failed to merge', 'Approved'] |
78 | 39 | 50 | ||
79 | 40 | #---------------------------------------------------------- | 51 | #---------------------------------------------------------- |
80 | 41 | # OpenERP RunBot misc | ||
81 | 42 | #---------------------------------------------------------- | ||
82 | 43 | |||
83 | 44 | def underscore(s): | ||
84 | 45 | return s.replace("~","").replace(":","_").replace("/","_").replace(".","_").replace(" ","_") | ||
85 | 46 | |||
86 | 47 | def get_committer_info(repo_path): | ||
87 | 48 | committer_name = None | ||
88 | 49 | committer_xgram = None | ||
89 | 50 | committer_email = None | ||
90 | 51 | |||
91 | 52 | output = run_output(["bzr", "log", "--long", "-r-1"], cwd=repo_path) | ||
92 | 53 | |||
93 | 54 | committer_re = re.compile('committer: *(.+)<(.+)@(.+)>') | ||
94 | 55 | for i in output.split('\n'): | ||
95 | 56 | m = committer_re.match(i) | ||
96 | 57 | if m: | ||
97 | 58 | committer_name = m.group(1).strip() | ||
98 | 59 | committer_xgram = m.group(2) | ||
99 | 60 | committer_email = m.group(2) + '@' + m.group(3) | ||
100 | 61 | break | ||
101 | 62 | |||
102 | 63 | return committer_name, committer_xgram, committer_email | ||
103 | 64 | |||
104 | 65 | def has_test_enable_flag(server_bin_path): | ||
105 | 66 | """ | ||
106 | 67 | Test whether an openerp-server executable has the --test-enable flag. | ||
107 | 68 | (When the flag is present, testing for success/failure is done in a | ||
108 | 69 | different way.) | ||
109 | 70 | """ | ||
110 | 71 | p1 = subprocess.Popen([server_bin_path, "--help"], | ||
111 | 72 | stdout=subprocess.PIPE) | ||
112 | 73 | p2 = subprocess.Popen(["grep", "test-enable"], stdin=p1.stdout, | ||
113 | 74 | stdout=subprocess.PIPE) | ||
114 | 75 | p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits. | ||
115 | 76 | output = p2.communicate()[0] | ||
116 | 77 | return output == " --test-enable Enable YAML and unit tests.\n" | ||
117 | 78 | |||
118 | 79 | #---------------------------------------------------------- | ||
119 | 80 | # OpenERP RunBot Branch | 52 | # OpenERP RunBot Branch |
120 | 81 | #---------------------------------------------------------- | 53 | #---------------------------------------------------------- |
121 | 82 | 54 | ||
122 | 83 | class RunBotBranch(object): | 55 | class RunBotBranch(object): |
123 | 84 | def __init__(self, runbot, branch, group=None, | 56 | def __init__(self, runbot, branch, group=None, |
125 | 85 | overriden_repo_path=None, overriden_project_name=None): | 57 | repo_path=None, project_name=None, trigger_build=False): |
126 | 86 | """ | 58 | """ |
127 | 87 | Represent a single Launchpad branch, e.g. | 59 | Represent a single Launchpad branch, e.g. |
128 | 88 | `~openerp-dev/openobject-server/trunk-foo-bar`. | 60 | `~openerp-dev/openobject-server/trunk-foo-bar`. |
129 | 89 | |||
130 | 90 | A branch has an overriden_repo_path when it is a trunk or 6.{0,1} branch | ||
131 | 91 | used to complement another 'real' branch. | ||
132 | 92 | """ | 61 | """ |
133 | 93 | self.runbot = runbot | 62 | self.runbot = runbot |
134 | 94 | self.group = group | 63 | self.group = group |
135 | 64 | self.trigger_build = trigger_build | ||
136 | 95 | 65 | ||
137 | 96 | # e.g. trunk | 66 | # e.g. trunk |
138 | 97 | self.name = branch.name | 67 | self.name = branch.name |
139 | @@ -100,13 +70,10 @@ | |||
140 | 100 | self.unique_name = branch.unique_name | 70 | self.unique_name = branch.unique_name |
141 | 101 | self.unique_name_underscore = underscore(self.unique_name) | 71 | self.unique_name_underscore = underscore(self.unique_name) |
142 | 102 | # server, addons, or web (both for openobject-client-web and openerp-web) | 72 | # server, addons, or web (both for openobject-client-web and openerp-web) |
145 | 103 | if overriden_project_name: | 73 | if project_name: |
146 | 104 | self.project_name = overriden_project_name | 74 | self.project_name = project_name |
147 | 105 | else: | 75 | else: |
152 | 106 | self.project_name = re.search("/(openobject|openerp)-(addons|server|client-web|web)/",self.unique_name).group(2) | 76 | self.project_name = project_name_from_unique_name(self.unique_name) |
149 | 107 | if self.project_name == 'client-web': | ||
150 | 108 | self.project_name = 'web' | ||
151 | 109 | assert self.project_name in ('server', 'addons', 'web') | ||
153 | 110 | 77 | ||
154 | 111 | self.lp_date_last_modified = branch.date_last_modified | 78 | self.lp_date_last_modified = branch.date_last_modified |
155 | 112 | self.lp_revision_count = branch.revision_count | 79 | self.lp_revision_count = branch.revision_count |
156 | @@ -116,10 +83,10 @@ | |||
157 | 116 | self.merge_count = 0 | 83 | self.merge_count = 0 |
158 | 117 | 84 | ||
159 | 118 | # Repository path <Root>/repo/<branchuniquename> | 85 | # Repository path <Root>/repo/<branchuniquename> |
164 | 119 | self.repo_path=os.path.join(self.runbot.wd,'repo',self.unique_name_underscore) | 86 | if repo_path: |
165 | 120 | self.overriden_repo_path = overriden_repo_path | 87 | self.repo_path=repo_path |
166 | 121 | if overriden_repo_path: | 88 | else: |
167 | 122 | self.repo_path=overriden_repo_path | 89 | self.repo_path=os.path.join(self.runbot.wd,'repo',self.unique_name_underscore) |
168 | 123 | 90 | ||
169 | 124 | self.committer_name = None | 91 | self.committer_name = None |
170 | 125 | self.committer_xgram = None | 92 | self.committer_xgram = None |
171 | @@ -134,7 +101,7 @@ | |||
172 | 134 | if self.local_revision_count != self.lp_revision_count: | 101 | if self.local_revision_count != self.lp_revision_count: |
173 | 135 | self.local_date_last_modified = self.lp_date_last_modified | 102 | self.local_date_last_modified = self.lp_date_last_modified |
174 | 136 | self.local_revision_count = self.lp_revision_count | 103 | self.local_revision_count = self.lp_revision_count |
176 | 137 | if not self.overriden_repo_path or self.group.is_configured(): | 104 | if self.trigger_build: |
177 | 138 | name = self.project_name | 105 | name = self.project_name |
178 | 139 | self.group.need_run_reason.append(name) | 106 | self.group.need_run_reason.append(name) |
179 | 140 | 107 | ||
180 | @@ -142,50 +109,87 @@ | |||
181 | 142 | # OpenERP RunBot Grouped Branch | 109 | # OpenERP RunBot Grouped Branch |
182 | 143 | #---------------------------------------------------------- | 110 | #---------------------------------------------------------- |
183 | 144 | 111 | ||
186 | 145 | class RunBotGroupedBranchBase(object): | 112 | class RunBotGroupedBranch(object): |
187 | 146 | def __init__(self, runbot, sticky): | 113 | """ |
188 | 114 | A single 'branch group' represents a single branch name for all our | ||
189 | 115 | projects. E.g. trunk is available in server, addons, web, and client-web. | ||
190 | 116 | |||
191 | 117 | When the corresponding project doesn't have that branch, trunk is used. | ||
192 | 118 | For instance, a branch group can represent | ||
193 | 119 | `~openerp-dev/openobject-server/trunk-foo-bar` | ||
194 | 120 | `~openerp-dev/openobject-addons/trunk-foo-bar` | ||
195 | 121 | and | ||
196 | 122 | `~openerp/openobject-web-client/6.0` | ||
197 | 123 | `~openerp/openerp-web/trunk` | ||
198 | 124 | will be used to complete the group, building the four branches together. | ||
199 | 125 | |||
200 | 126 | Alternatively a 'branch group' can be configured. In that case, all | ||
201 | 127 | branches are manually specified; no attempt is done to complete the group. | ||
202 | 128 | """ | ||
203 | 129 | def __init__(self, runbot, team_name, name, version, sticky, | ||
204 | 130 | server_branch=None, client_web_branch=None, | ||
205 | 131 | web_branch=None, addons_branches=None, modules=None, job_type=None): | ||
206 | 147 | self.runbot = runbot | 132 | self.runbot = runbot |
207 | 148 | self.sticky = sticky | 133 | self.sticky = sticky |
208 | 134 | self.job_type = job_type | ||
209 | 135 | |||
210 | 149 | # Points are build 'slots' | 136 | # Points are build 'slots' |
211 | 150 | self.points = [None for x in xrange(POINTS)] | 137 | self.points = [None for x in xrange(POINTS)] |
212 | 138 | |||
213 | 139 | self.team_name = team_name | ||
214 | 140 | self.name = name # group name or manually chosen name. | ||
215 | 141 | self.name_underscore = underscore(self.name) | ||
216 | 142 | self.version = version # '6.0', '6.1', or 'trunk' | ||
217 | 143 | |||
218 | 151 | # Reason to do a build, e.g. because 'addons' was commited to. | 144 | # Reason to do a build, e.g. because 'addons' was commited to. |
219 | 152 | self.need_run_reason = [] | 145 | self.need_run_reason = [] |
221 | 153 | self.version = None | 146 | |
222 | 147 | self.server_branch = server_branch | ||
223 | 148 | self.web_branch = client_web_branch or web_branch | ||
224 | 149 | self.addons_branches = addons_branches | ||
225 | 150 | |||
226 | 151 | self.server = None | ||
227 | 152 | self.web = None | ||
228 | 153 | self.addons = [] | ||
229 | 154 | |||
230 | 155 | self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-'))) | ||
231 | 156 | |||
232 | 157 | # List of modules to install instead of `all`. | ||
233 | 158 | self.modules = modules.split(',') if modules else [] | ||
234 | 159 | |||
235 | 160 | self.configured = False | ||
236 | 161 | |||
237 | 162 | # A normal build_number is maxint, a build_number is assigned when a | ||
238 | 163 | # build is manually requested or a triggering branch has a new revision. | ||
239 | 164 | # The build number is used to assign a position in the queue. | ||
240 | 165 | # The value will be reset to maxint after the build is done. | ||
241 | 166 | self.build_number = sys.maxint | ||
242 | 167 | |||
243 | 154 | # True when it is not possible to provide default branches based on | 168 | # True when it is not possible to provide default branches based on |
244 | 155 | # the name. | 169 | # the name. |
245 | 156 | self.wrong_matching = False | 170 | self.wrong_matching = False |
246 | 157 | 171 | ||
247 | 158 | # A normal manual_build is maxint, a manual_build is used only when | ||
248 | 159 | # manually requesting a build with a 'build' command pushed in the | ||
249 | 160 | # queue. The value will be reset to maxint after the build is done. | ||
250 | 161 | self.manual_build = sys.maxint | ||
251 | 162 | |||
252 | 163 | # List of modules to install instead of `all`. | ||
253 | 164 | self.modules = [] | ||
254 | 165 | |||
255 | 166 | def add_point(self, j): | 172 | def add_point(self, j): |
259 | 167 | assert not j.completed | 173 | assert j.state == STATE_ALLOCATED |
260 | 168 | p = Point(self, j) | 174 | self.points = self.points[1:] + [j] # TODO delete db and on-disk data |
258 | 169 | self.points = self.points[1:] + [p] # TODO delete db and on-disk data | ||
261 | 170 | 175 | ||
262 | 171 | def complete_point(self, j): | 176 | def complete_point(self, j): |
264 | 172 | assert j.completed | 177 | assert j.state in (STATE_RUNNING, STATE_BROKEN) |
265 | 173 | for p in self.points + [None]: | 178 | for p in self.points + [None]: |
266 | 174 | if p and p.job_id == j.job_id: | 179 | if p and p.job_id == j.job_id: |
267 | 175 | break | 180 | break |
270 | 176 | if p and p.state != 'running': | 181 | if p and p.state != j.state: |
269 | 177 | p.update(j) | ||
271 | 178 | p.save_json() | 182 | p.save_json() |
272 | 179 | 183 | ||
273 | 180 | def all_points_completed(self): | 184 | def all_points_completed(self): |
274 | 181 | for p in self.points: | 185 | for p in self.points: |
276 | 182 | if p is not None and p.state != 'running': | 186 | if p is not None and p.state not in (STATE_RUNNING, STATE_BROKEN): |
277 | 183 | return False | 187 | return False |
278 | 184 | return True | 188 | return True |
279 | 185 | 189 | ||
280 | 186 | def is_sticky_job(self, j): | 190 | def is_sticky_job(self, j): |
281 | 187 | """ Return True if the job is the latest running point of a sticky branch. """ | 191 | """ Return True if the job is the latest running point of a sticky branch. """ |
283 | 188 | runnings = filter(lambda x: x and x.running_t0, self.points) | 192 | runnings = filter(lambda x: x and x.state == STATE_RUNNING, self.points) |
284 | 189 | return self.sticky and runnings and runnings[-1].job_id == j.job_id | 193 | return self.sticky and runnings and runnings[-1].job_id == j.job_id |
285 | 190 | 194 | ||
286 | 191 | def update_state(self, state): | 195 | def update_state(self, state): |
287 | @@ -198,43 +202,33 @@ | |||
288 | 198 | if self.sticky != previous: | 202 | if self.sticky != previous: |
289 | 199 | log("stickiness changed", team=self.team_name, branch=self.name, now=self.sticky) | 203 | log("stickiness changed", team=self.team_name, branch=self.name, now=self.sticky) |
290 | 200 | 204 | ||
315 | 201 | def is_configured(self): | 205 | def repo_updates(self): |
316 | 202 | return isinstance(self, ConfiguredGroup) | 206 | return [copy.copy(x) for x in self.all_branches()] |
317 | 203 | 207 | ||
318 | 204 | class ConfiguredGroup(RunBotGroupedBranchBase): | 208 | def all_branches(self): |
319 | 205 | def __init__(self, runbot, team_name, name, version, sticky, | 209 | r = [] |
320 | 206 | server_branch=None, client_web_branch=None, | 210 | r.append(self.server) |
321 | 207 | web_branch=None, addons_branches=None, modules=None): | 211 | r.extend([x for x in self.addons]) |
322 | 208 | super(ConfiguredGroup, self).__init__(runbot, sticky) | 212 | r.append(self.web) |
323 | 209 | self.team_name = team_name | 213 | if hasattr(self, 'migration_script'): |
324 | 210 | self.name = name # Unique manually-chosen name, not necessarily the group name. | 214 | r.extend([self.migration_script, self.migration_platform]) |
325 | 211 | self.name_underscore = underscore(self.name) | 215 | return r |
326 | 212 | self.version = version # '6.0', '6.1', or 'trunk' | 216 | |
327 | 213 | 217 | def lp_date_last_modified(self): | |
328 | 214 | self.server_branch = server_branch | 218 | return max(p.lp_date_last_modified for p in self.all_branches() if p and p.trigger_build) |
305 | 215 | self.web_branch = client_web_branch or web_branch | ||
306 | 216 | self.addons_branches = addons_branches | ||
307 | 217 | |||
308 | 218 | self.server = None | ||
309 | 219 | self.web = None | ||
310 | 220 | self.addons = [] | ||
311 | 221 | |||
312 | 222 | self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-'))) | ||
313 | 223 | |||
314 | 224 | self.modules = modules.split(',') if modules else [] | ||
329 | 225 | 219 | ||
330 | 226 | def is_ok(self): | 220 | def is_ok(self): |
331 | 227 | """ | 221 | """ |
333 | 228 | Test whether the group is useable, i.e. if self.populate_branches() | 222 | Test whether the group is useable, i.e. if self.configure_branches() |
334 | 229 | didn't early return. | 223 | didn't early return. |
335 | 230 | """ | 224 | """ |
340 | 231 | if self.server and self.web and self.addons: | 225 | return self.server and self.web and self.addons |
337 | 232 | return True | ||
338 | 233 | else: | ||
339 | 234 | return False | ||
341 | 235 | 226 | ||
343 | 236 | def populate_branches(self, launchpad): | 227 | # TODO all the xxxx_branch argument in __init__ can be moved here. |
344 | 228 | def configure_branches(self, launchpad): | ||
345 | 229 | """ Fetch the configured branches. """ | ||
346 | 237 | log("runbot-populate-configured-branches") | 230 | log("runbot-populate-configured-branches") |
347 | 231 | self.configured = True | ||
348 | 238 | repo = os.path.join(self.runbot.wd, 'repo') | 232 | repo = os.path.join(self.runbot.wd, 'repo') |
349 | 239 | filters = {'status': NEW_MERGE_STATUS} | 233 | filters = {'status': NEW_MERGE_STATUS} |
350 | 240 | 234 | ||
351 | @@ -243,7 +237,7 @@ | |||
352 | 243 | if not b: | 237 | if not b: |
353 | 244 | log("WARNING:no such unique name", name=self.server_branch) | 238 | log("WARNING:no such unique name", name=self.server_branch) |
354 | 245 | return | 239 | return |
356 | 246 | self.server = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='server') | 240 | self.server = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='server', trigger_build=True) |
357 | 247 | self.server.update_launchpad(b) | 241 | self.server.update_launchpad(b) |
358 | 248 | self.server.merge_count = len(list(b.getMergeProposals(**filters))) | 242 | self.server.merge_count = len(list(b.getMergeProposals(**filters))) |
359 | 249 | 243 | ||
360 | @@ -253,7 +247,7 @@ | |||
361 | 253 | if not b: | 247 | if not b: |
362 | 254 | log("WARNING:no such unique name", name=self.web_branch) | 248 | log("WARNING:no such unique name", name=self.web_branch) |
363 | 255 | return | 249 | return |
365 | 256 | self.web = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='web') | 250 | self.web = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='web', trigger_build=True) |
366 | 257 | self.web.update_launchpad(b) | 251 | self.web.update_launchpad(b) |
367 | 258 | self.web.merge_count = len(list(b.getMergeProposals(**filters))) | 252 | self.web.merge_count = len(list(b.getMergeProposals(**filters))) |
368 | 259 | else: | 253 | else: |
369 | @@ -267,59 +261,13 @@ | |||
370 | 267 | log("WARNING:no such unique name", name=self.addons_branches[x]) | 261 | log("WARNING:no such unique name", name=self.addons_branches[x]) |
371 | 268 | self.addons = [] | 262 | self.addons = [] |
372 | 269 | return | 263 | return |
374 | 270 | bb = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='addons') | 264 | bb = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='addons', trigger_build=True) |
375 | 271 | bb.update_launchpad(b) | 265 | bb.update_launchpad(b) |
376 | 272 | bb.merge_count = len(list(b.getMergeProposals(**filters))) | 266 | bb.merge_count = len(list(b.getMergeProposals(**filters))) |
377 | 273 | self.addons.append(bb) | 267 | self.addons.append(bb) |
378 | 274 | 268 | ||
379 | 275 | def repo_updates(self): | ||
380 | 276 | r = [] | ||
381 | 277 | r.append(copy.copy(self.server)) | ||
382 | 278 | for x in self.addons: | ||
383 | 279 | r.append(copy.copy(x)) | ||
384 | 280 | r.append(copy.copy(self.web)) | ||
385 | 281 | return r | ||
386 | 282 | |||
387 | 283 | def lp_date_last_modified(self): | ||
388 | 284 | lp_date_last_modified = None | ||
389 | 285 | for p in [self.server, self.web] + self.addons: | ||
390 | 286 | if p and (not lp_date_last_modified or p.lp_date_last_modified > lp_date_last_modified): | ||
391 | 287 | lp_date_last_modified = p.lp_date_last_modified | ||
392 | 288 | assert lp_date_last_modified # at least one project in the group | ||
393 | 289 | return lp_date_last_modified | ||
394 | 290 | |||
395 | 291 | class RunBotGroupedBranch(RunBotGroupedBranchBase): | ||
396 | 292 | """ | ||
397 | 293 | A single 'branch group' represents a single branch name for all our | ||
398 | 294 | projects. E.g. trunk is available in server, addons, web, and client-web. | ||
399 | 295 | |||
400 | 296 | When the corresponding project doesn't have that branch, trunk is used. | ||
401 | 297 | For instance, a branch group can represent | ||
402 | 298 | `~openerp-dev/openobject-server/trunk-foo-bar` | ||
403 | 299 | `~openerp-dev/openobject-addons/trunk-foo-bar` | ||
404 | 300 | and | ||
405 | 301 | `~openerp/openobject-web-client/6.0` | ||
406 | 302 | `~openerp/openerp-web/trunk` | ||
407 | 303 | will be used to complete the group, building the four branches together. | ||
408 | 304 | |||
409 | 305 | Alternatively a 'branch group' can be configured. In that case, all | ||
410 | 306 | branches are manually specified; no attempt is done to complete the group. | ||
411 | 307 | """ | ||
412 | 308 | |||
413 | 309 | def __init__(self, runbot, b, team_name, sticky): | ||
414 | 310 | """A grouped branch can be created from any branch.""" | ||
415 | 311 | super(RunBotGroupedBranch, self).__init__(runbot, sticky) | ||
416 | 312 | self.team_name = team_name | ||
417 | 313 | self.name = b.name | ||
418 | 314 | self.name_underscore = underscore(self.name) | ||
419 | 315 | self.server = None | ||
420 | 316 | self.addons = [] # Only one branch in a non-configured group. | ||
421 | 317 | self.web = None | ||
422 | 318 | self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-'))) | ||
423 | 319 | |||
424 | 320 | self.add_branch(b) | ||
425 | 321 | |||
426 | 322 | def add_branch(self, b): | 269 | def add_branch(self, b): |
427 | 270 | """ Add a single branch (when not using configure_branches(). """ | ||
428 | 323 | assert b.name == self.name | 271 | assert b.name == self.name |
429 | 324 | b.group = self | 272 | b.group = self |
430 | 325 | if b.project_name == 'addons': | 273 | if b.project_name == 'addons': |
431 | @@ -363,403 +311,28 @@ | |||
432 | 363 | for p in ('server', 'web'): | 311 | for p in ('server', 'web'): |
433 | 364 | if not getattr(self, p): | 312 | if not getattr(self, p): |
434 | 365 | b = self.runbot.main_branches[p+ending] | 313 | b = self.runbot.main_branches[p+ending] |
436 | 366 | setattr(self, p, RunBotBranch(self.runbot,b,group=self,overriden_repo_path=paths[p])) | 314 | setattr(self, p, RunBotBranch(self.runbot,b,group=self,repo_path=paths[p])) |
437 | 367 | getattr(self, p).update_launchpad(b) | 315 | getattr(self, p).update_launchpad(b) |
439 | 368 | elif getattr(self, p).overriden_repo_path: | 316 | elif not getattr(self, p).trigger_build: |
440 | 369 | b = self.runbot.main_branches[p+ending] | 317 | b = self.runbot.main_branches[p+ending] |
441 | 370 | getattr(self, p).update_launchpad(b) | 318 | getattr(self, p).update_launchpad(b) |
442 | 371 | if not self.addons: | 319 | if not self.addons: |
443 | 372 | b = self.runbot.main_branches['addons'+ending] | 320 | b = self.runbot.main_branches['addons'+ending] |
445 | 373 | self.addons = [RunBotBranch(self.runbot,b,group=self,overriden_repo_path=paths['addons'])] | 321 | self.addons = [RunBotBranch(self.runbot,b,group=self,repo_path=paths['addons'])] |
446 | 374 | self.addons[0].update_launchpad(b) | 322 | self.addons[0].update_launchpad(b) |
448 | 375 | elif self.addons[0].overriden_repo_path: | 323 | elif not self.addons[0].trigger_build: |
449 | 376 | b = self.runbot.main_branches['addons'+ending] | 324 | b = self.runbot.main_branches['addons'+ending] |
450 | 377 | self.addons[0].update_launchpad(b) | 325 | self.addons[0].update_launchpad(b) |
451 | 378 | 326 | ||
836 | 379 | def repo_updates(self): | 327 | if self.job_type == 'migrate_script': |
837 | 380 | r = [] | 328 | if not hasattr(self, 'migration_script'): |
838 | 381 | r.append(copy.copy(self.server)) | 329 | self.migration_script = RunBotBranch(self.runbot, |
839 | 382 | r.append(copy.copy(self.addons[0])) | 330 | migration_scripts_branch, group=self, repo_path='repo/openerp_migration_script', project_name='migration-scripts', trigger_build=True) |
840 | 383 | r.append(copy.copy(self.web)) | 331 | self.migration_platform = RunBotBranch(self.runbot, |
841 | 384 | return r | 332 | migration_platform_branch, group=self, repo_path='repo/openerp_migration_platform', project_name='migration-platform', trigger_build=True) |
842 | 385 | 333 | self.migration_script.update_launchpad(migration_scripts_branch) | |
843 | 386 | def lp_date_last_modified(self): | 334 | self.migration_platform.update_launchpad(migration_platform_branch) |
844 | 387 | lp_date_last_modified = None | 335 | print "initialized group with job type <migration_script>" |
461 | 388 | for p in ('server', 'web'): | ||
462 | 389 | if getattr(self, p) and not getattr(self, p).overriden_repo_path \ | ||
463 | 390 | and (not lp_date_last_modified or getattr(self, p).lp_date_last_modified > lp_date_last_modified): | ||
464 | 391 | lp_date_last_modified = getattr(self, p).lp_date_last_modified | ||
465 | 392 | if self.addons and not self.addons[0].overriden_repo_path \ | ||
466 | 393 | and (not lp_date_last_modified or self.addons[0].lp_date_last_modified > lp_date_last_modified): | ||
467 | 394 | lp_date_last_modified = self.addons[0].lp_date_last_modified | ||
468 | 395 | assert lp_date_last_modified # at least one project in the group | ||
469 | 396 | return lp_date_last_modified | ||
470 | 397 | |||
471 | 398 | #---------------------------------------------------------- | ||
472 | 399 | # OpenERP RunBot Worker | ||
473 | 400 | #---------------------------------------------------------- | ||
474 | 401 | |||
475 | 402 | class Job(object): | ||
476 | 403 | """ | ||
477 | 404 | A Job encapsulates all the necessary data to build a branch group for a | ||
478 | 405 | given slot. The build is done in its own worker thread. | ||
479 | 406 | """ | ||
480 | 407 | |||
481 | 408 | def __init__(self, g, port, test, job_id, debug): | ||
482 | 409 | self.job_id = job_id | ||
483 | 410 | self.completed = False | ||
484 | 411 | self.team_name = g.team_name | ||
485 | 412 | self.name = g.name | ||
486 | 413 | self.name_underscore = g.name_underscore | ||
487 | 414 | self.version = g.version | ||
488 | 415 | self.debug = debug | ||
489 | 416 | |||
490 | 417 | self.repo_updates = g.repo_updates() | ||
491 | 418 | self.port = port | ||
492 | 419 | self.test = test | ||
493 | 420 | self.running_server_pid = 0 | ||
494 | 421 | self.client_web_pid = 0 | ||
495 | 422 | self.running_t0=0 | ||
496 | 423 | |||
497 | 424 | repo = os.path.join(g.runbot.wd,'repo') | ||
498 | 425 | self.server_src = g.server.repo_path | ||
499 | 426 | self.client_web_src = g.web.repo_path if g.version == '6.0' and g.web else None | ||
500 | 427 | self.web_src = g.web.repo_path if g.web else None | ||
501 | 428 | |||
502 | 429 | # if addons is not the full addons branch use trunk | ||
503 | 430 | self.addons_src = [a.repo_path for a in g.addons] | ||
504 | 431 | |||
505 | 432 | # Running path <Root>/static/<domain> | ||
506 | 433 | self.subdomain = "%s-%s-%s"%(self.team_name, self.name.replace('_','-').replace('.','-'),self.job_id) | ||
507 | 434 | self.running_path = os.path.join(g.runbot.wd,'static',self.subdomain) | ||
508 | 435 | self.json_path = g.json_path | ||
509 | 436 | self.log_path = os.path.join(self.running_path,'logs') | ||
510 | 437 | self.flags_path = os.path.join(self.running_path,'flags') | ||
511 | 438 | |||
512 | 439 | # Server | ||
513 | 440 | self.server_path=os.path.join(self.running_path,"server") | ||
514 | 441 | self.server_bin_path=os.path.join(self.server_path,"openerp-server") | ||
515 | 442 | self.server_log_path=os.path.join(self.log_path,'server.txt') | ||
516 | 443 | self.server_log_base_path=os.path.join(self.log_path,'test-base.txt') | ||
517 | 444 | self.server_log_all_path=os.path.join(self.log_path,'test-all.txt') | ||
518 | 445 | |||
519 | 446 | # coverage | ||
520 | 447 | self.coverage_file_path=os.path.join(self.log_path,'coverage.pickle') | ||
521 | 448 | self.coverage_base_path=os.path.join(self.log_path,'coverage-base') | ||
522 | 449 | self.coverage_all_path=os.path.join(self.log_path,'coverage-all') | ||
523 | 450 | |||
524 | 451 | # Web60 | ||
525 | 452 | self.client_web_pid=None | ||
526 | 453 | self.client_web_path=os.path.join(self.running_path,"client-web") | ||
527 | 454 | self.client_web_bin_path=os.path.join(self.client_web_path,"openerp-web.py") | ||
528 | 455 | self.client_web_doc_path=os.path.join(self.client_web_path,"doc") | ||
529 | 456 | self.client_web_log_path=os.path.join(self.log_path,'client-web.txt') | ||
530 | 457 | |||
531 | 458 | # test | ||
532 | 459 | self.test_base_result=None | ||
533 | 460 | self.test_base_path=os.path.join(self.log_path,'test-base.txt') | ||
534 | 461 | self.test_all_result=None | ||
535 | 462 | self.test_all_path=os.path.join(self.log_path,'test-all.txt') | ||
536 | 463 | |||
537 | 464 | self.server_net_port=g.runbot.server_net_port | ||
538 | 465 | self.server_xml_port=g.runbot.server_xml_port | ||
539 | 466 | self.client_web_port=g.runbot.client_web_port | ||
540 | 467 | |||
541 | 468 | self.db = self.name_underscore + '_' + str(self.job_id) | ||
542 | 469 | self.db_all = "%s_all" % self.db | ||
543 | 470 | |||
544 | 471 | for p in ('addons', 'server', 'web'): | ||
545 | 472 | setattr(self, p + '_committer_name', None) | ||
546 | 473 | setattr(self, p + '_committer_xgram', None) | ||
547 | 474 | setattr(self, p + '_committer_email', None) | ||
548 | 475 | self.start_time = 0 | ||
549 | 476 | self.completed_time = 0 | ||
550 | 477 | |||
551 | 478 | self.modules = g.modules | ||
552 | 479 | |||
553 | 480 | def spawn(self): | ||
554 | 481 | log("runbot-spawn-worker-" + str(self.job_id), group=self.name) | ||
555 | 482 | t = threading.Thread(target=self.work, name=('runbot-group-worker-' + str(self.job_id))) | ||
556 | 483 | t.daemon = True | ||
557 | 484 | t.start() | ||
558 | 485 | |||
559 | 486 | def work(self): | ||
560 | 487 | try: | ||
561 | 488 | self.pull_branches() | ||
562 | 489 | self.start_time = time.time() | ||
563 | 490 | self.start() | ||
564 | 491 | self.completed_time = time.time() | ||
565 | 492 | self.completed = True | ||
566 | 493 | log("runbot-end-worker", job=self.name) | ||
567 | 494 | except Exception, e: | ||
568 | 495 | self.completed = True | ||
569 | 496 | log("runbot-end-worker [with exception]", job=self.name) | ||
570 | 497 | print traceback.format_exc() | ||
571 | 498 | |||
572 | 499 | def pull_branches(self): | ||
573 | 500 | for b in self.repo_updates: | ||
574 | 501 | log("branch-update",branch=b.unique_name) | ||
575 | 502 | if os.path.exists(b.repo_path): | ||
576 | 503 | run(["bzr", "pull", "-d", b.repo_path, "--overwrite"]) | ||
577 | 504 | else: | ||
578 | 505 | run(["bzr", "branch", "lp:%s"%b.unique_name, b.repo_path]) | ||
579 | 506 | run(["bzr", "update", "-r", str(b.local_revision_count), b.repo_path]) | ||
580 | 507 | |||
581 | 508 | committer_name, committer_xgram, committer_email = \ | ||
582 | 509 | get_committer_info(b.repo_path) | ||
583 | 510 | b.committer_name = committer_name | ||
584 | 511 | b.committer_xgram = committer_xgram | ||
585 | 512 | b.committer_email = committer_email | ||
586 | 513 | |||
587 | 514 | def start_rsync(self): | ||
588 | 515 | log("job-start-rsync",branch=self.name) | ||
589 | 516 | for i in [self.running_path,self.log_path,self.flags_path,self.client_web_doc_path]: | ||
590 | 517 | if not os.path.exists(i): | ||
591 | 518 | os.makedirs(i) | ||
592 | 519 | # copy server | ||
593 | 520 | run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.server_src, self.server_path]) | ||
594 | 521 | |||
595 | 522 | # copy addons (6.0 uses bin/addons, 6.1 and trunk use openerp/addons) | ||
596 | 523 | addons_dest = "openerp/addons" | ||
597 | 524 | if not os.path.exists(os.path.join(self.server_path,addons_dest)): | ||
598 | 525 | addons_dest = "bin/addons" | ||
599 | 526 | addons_path = os.path.join(self.server_path,addons_dest) | ||
600 | 527 | for a in self.addons_src: | ||
601 | 528 | run(["rsync","-a","--exclude",".bzr", "%s/"%a, addons_path]) | ||
602 | 529 | if self.web_src and self.version != '6.0': | ||
603 | 530 | run(["rsync","-a","--exclude",".bzr", "%s/addons/"%self.web_src, addons_path]) | ||
604 | 531 | |||
605 | 532 | # copy web-client | ||
606 | 533 | if self.client_web_src: | ||
607 | 534 | run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.client_web_src, self.client_web_path]) | ||
608 | 535 | |||
609 | 536 | def start_createdb(self): | ||
610 | 537 | run(["psql","template1","-c","select pg_terminate_backend(procpid) from pg_stat_activity where datname in ('%s','%s')"%(self.db,self.db_all)]) | ||
611 | 538 | time.sleep(3) | ||
612 | 539 | run(["dropdb",self.db]) | ||
613 | 540 | run(["dropdb",self.db_all]) | ||
614 | 541 | run(["createdb",self.db]) | ||
615 | 542 | run(["createdb",self.db_all]) | ||
616 | 543 | |||
617 | 544 | def run_log(self, cmd, logfile, env=None): | ||
618 | 545 | env = dict(os.environ, **env) if env else None | ||
619 | 546 | log("run", *cmd, logfile=logfile) | ||
620 | 547 | out=open(logfile,"w") | ||
621 | 548 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True, env=env) | ||
622 | 549 | self.running_server_pid=p.pid | ||
623 | 550 | p.communicate() | ||
624 | 551 | return p | ||
625 | 552 | |||
626 | 553 | def resolve_server_bin_path(self): | ||
627 | 554 | # This can be done only if the files are present otherwise the if | ||
628 | 555 | # will always fail. Alternatively, server_bin_path could be a property. | ||
629 | 556 | if not os.path.exists(self.server_bin_path): # for 6.0 branches | ||
630 | 557 | self.server_bin_path=os.path.join(self.server_path,"bin","openerp-server.py") | ||
631 | 558 | |||
632 | 559 | def start_test_base(self): | ||
633 | 560 | log("job-start-server-base") | ||
634 | 561 | cmd = [self.server_bin_path,"-d",self.db,"-i","base","--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"] | ||
635 | 562 | _has_test_enable_flag = False | ||
636 | 563 | if has_test_enable_flag(self.server_bin_path): | ||
637 | 564 | cmd.append("--test-enable") | ||
638 | 565 | _has_test_enable_flag = True | ||
639 | 566 | cmd = ["coverage","run","--branch"] + cmd | ||
640 | 567 | self.run_log(cmd, logfile=self.test_base_path,env={'COVERAGE_FILE': self.coverage_file_path}) | ||
641 | 568 | run(["coverage","html","-d",self.coverage_base_path,"--ignore-errors","--include=*.py"],env={'COVERAGE_FILE': self.coverage_file_path}) | ||
642 | 569 | if _has_test_enable_flag: | ||
643 | 570 | success_message = "openerp.modules.loading: Modules loaded." | ||
644 | 571 | rc = not bool(run(["grep",success_message,self.test_base_path])) | ||
645 | 572 | else: | ||
646 | 573 | rc = bool(run(["grep","Traceback",self.test_base_path])) | ||
647 | 574 | self.test_base_result = rc | ||
648 | 575 | |||
649 | 576 | def start_test_all(self): | ||
650 | 577 | log("job-start-server-all") | ||
651 | 578 | mods = [] | ||
652 | 579 | if not os.path.exists(os.path.join(self.server_path,"openerp/addons")): | ||
653 | 580 | mods = os.listdir(os.path.join(self.server_path,"bin/addons")) | ||
654 | 581 | elif os.path.exists(os.path.join(self.server_path,"openerp/addons")): | ||
655 | 582 | mods = os.listdir(os.path.join(self.server_path,"openerp/addons")) | ||
656 | 583 | bl = [ | ||
657 | 584 | 'document_ftp', 'l10n_lu', | ||
658 | 585 | '.bzrignore', '__init__.pyc', '__init__.py', 'base_quality_interrogation.py', | ||
659 | 586 | ] | ||
660 | 587 | mods = [m for m in mods if m not in bl] | ||
661 | 588 | if self.modules: | ||
662 | 589 | # TODO feedback when a requested modules does'nt exist. | ||
663 | 590 | mods = [m for m in mods if m in self.modules] | ||
664 | 591 | mods = ",".join(mods) | ||
665 | 592 | cmd = [self.server_bin_path,"-d",self.db_all,"-i",mods,"--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"] | ||
666 | 593 | _has_test_enable_flag = False | ||
667 | 594 | if has_test_enable_flag(self.server_bin_path): | ||
668 | 595 | cmd.append("--test-enable") | ||
669 | 596 | _has_test_enable_flag = True | ||
670 | 597 | cmd = ["coverage","run","--branch"] + cmd | ||
671 | 598 | self.run_log(cmd, logfile=self.test_all_path) | ||
672 | 599 | run(["coverage","html","-d",self.coverage_all_path,"--ignore-errors","--include=*.py"]) | ||
673 | 600 | if _has_test_enable_flag: | ||
674 | 601 | success_message = "openerp.modules.loading: Modules loaded." | ||
675 | 602 | rc = not bool(run(["grep",success_message,self.test_all_path])) | ||
676 | 603 | else: | ||
677 | 604 | rc = bool(run(["grep","Traceback",self.test_all_path])) | ||
678 | 605 | self.test_all_result = rc | ||
679 | 606 | |||
680 | 607 | def start_server(self): | ||
681 | 608 | port = self.port | ||
682 | 609 | log("job-start-server",branch=self.name,port=port) | ||
683 | 610 | cmd=[self.server_bin_path,"--no-xmlrpcs","--netrpc-port=%d"%(self.server_net_port+port),"--xmlrpc-port=%d"%(self.server_xml_port+port)] | ||
684 | 611 | if os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi.py')) \ | ||
685 | 612 | or os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi', 'core.py')): | ||
686 | 613 | cmd+=["--db-filter=%d(_.*)?$","--load=web"] | ||
687 | 614 | log("run",*cmd,log=self.server_log_path) | ||
688 | 615 | out=open(self.server_log_path,"w") | ||
689 | 616 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) | ||
690 | 617 | self.running_server_pid=p.pid | ||
691 | 618 | |||
692 | 619 | def start_client_web(self): | ||
693 | 620 | if not self.client_web_src: | ||
694 | 621 | return | ||
695 | 622 | port = self.port | ||
696 | 623 | log("job-start-client-web",branch=self.name,port=port) | ||
697 | 624 | config=""" | ||
698 | 625 | [global] | ||
699 | 626 | server.environment = "development" | ||
700 | 627 | server.socket_host = "0.0.0.0" | ||
701 | 628 | server.socket_port = %d | ||
702 | 629 | server.thread_pool = 10 | ||
703 | 630 | tools.sessions.on = True | ||
704 | 631 | log.access_level = "INFO" | ||
705 | 632 | log.error_level = "INFO" | ||
706 | 633 | tools.csrf.on = False | ||
707 | 634 | tools.log_tracebacks.on = False | ||
708 | 635 | tools.cgitb.on = True | ||
709 | 636 | openerp.server.host = 'localhost' | ||
710 | 637 | openerp.server.port = '%d' | ||
711 | 638 | openerp.server.protocol = 'socket' | ||
712 | 639 | openerp.server.timeout = 450 | ||
713 | 640 | [openerp-web] | ||
714 | 641 | dblist.filter = 'BOTH' | ||
715 | 642 | dbbutton.visible = True | ||
716 | 643 | company.url = '' | ||
717 | 644 | openerp.server.host = 'localhost' | ||
718 | 645 | openerp.server.port = '%d' | ||
719 | 646 | openerp.server.protocol = 'socket' | ||
720 | 647 | openerp.server.timeout = 450 | ||
721 | 648 | """%(self.client_web_port+port,self.server_net_port+port,self.server_net_port+port) | ||
722 | 649 | config=config.replace("\n ","\n") | ||
723 | 650 | cfgs = [os.path.join(self.client_web_path,"doc","openerp-web.cfg"), os.path.join(self.client_web_path,"openerp-web.cfg")] | ||
724 | 651 | for i in cfgs: | ||
725 | 652 | f=open(i,"w") | ||
726 | 653 | f.write(config) | ||
727 | 654 | f.close() | ||
728 | 655 | |||
729 | 656 | cmd=[self.client_web_bin_path] | ||
730 | 657 | log("run",*cmd,log=self.client_web_log_path) | ||
731 | 658 | out=open(self.client_web_log_path,"w") | ||
732 | 659 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) | ||
733 | 660 | self.client_web_pid=p.pid | ||
734 | 661 | |||
735 | 662 | def start(self): | ||
736 | 663 | log("job-start",branch=self.name,port=self.port) | ||
737 | 664 | self.start_rsync() | ||
738 | 665 | self.resolve_server_bin_path() | ||
739 | 666 | self.start_createdb() | ||
740 | 667 | try: | ||
741 | 668 | if self.test: | ||
742 | 669 | self.start_test_base() | ||
743 | 670 | if not self.debug: | ||
744 | 671 | self.start_test_all() | ||
745 | 672 | else: | ||
746 | 673 | self.test_all_result = True | ||
747 | 674 | self.start_server() | ||
748 | 675 | self.start_client_web() | ||
749 | 676 | except OSError,e: | ||
750 | 677 | log("branch-start-error",exception=e) | ||
751 | 678 | except IOError,e: | ||
752 | 679 | log("branch-start-error",exception=e) | ||
753 | 680 | self.running_t0=time.time() | ||
754 | 681 | log("branch-started",branch=self.name,port=self.port) | ||
755 | 682 | |||
756 | 683 | def stop(self): | ||
757 | 684 | log("Stopping job", id=self.job_id, branch=self.name) | ||
758 | 685 | if self.running_server_pid: | ||
759 | 686 | kill(self.running_server_pid) | ||
760 | 687 | if self.client_web_pid: | ||
761 | 688 | kill(self.client_web_pid) | ||
762 | 689 | |||
763 | 690 | #---------------------------------------------------------- | ||
764 | 691 | # OpenERP RunBot Build Slot | ||
765 | 692 | #---------------------------------------------------------- | ||
766 | 693 | |||
767 | 694 | class Point(object): | ||
768 | 695 | """A point is a build slot and associated to a worker thread.""" | ||
769 | 696 | def __init__(self, g, j): | ||
770 | 697 | """Create a Point from a given group and job.""" | ||
771 | 698 | self.version = g.version | ||
772 | 699 | self.port = j.port | ||
773 | 700 | self.job_id = j.job_id | ||
774 | 701 | self.team_name = j.team_name | ||
775 | 702 | self.name_underscore = j.name_underscore | ||
776 | 703 | self.db = j.db | ||
777 | 704 | self.running_path = j.running_path | ||
778 | 705 | self.json_path = j.json_path | ||
779 | 706 | self.subdomain = j.subdomain | ||
780 | 707 | self.repo_updates = j.repo_updates # committer date not yet available, available after j.work() is called. | ||
781 | 708 | self.server_rev = g.server.local_revision_count | ||
782 | 709 | self.addons_rev = [a.local_revision_count for a in g.addons] | ||
783 | 710 | self.web_rev = g.web.local_revision_count if g.web else 0 | ||
784 | 711 | self.need_run_reason = g.need_run_reason | ||
785 | 712 | for p in ('addons', 'server', 'web'): | ||
786 | 713 | setattr(self, p + '_committer_name', getattr(j, p + '_committer_name')) | ||
787 | 714 | setattr(self, p + '_committer_xgram', getattr(j, p + '_committer_xgram')) | ||
788 | 715 | setattr(self, p + '_committer_email', getattr(j, p + '_committer_email')) | ||
789 | 716 | self.update(j) | ||
790 | 717 | self.manual_build = g.manual_build | ||
791 | 718 | |||
792 | 719 | def update(self, j): | ||
793 | 720 | """ | ||
794 | 721 | Update the Point from a job. The job should be the same than the one | ||
795 | 722 | used in `__init__()`. | ||
796 | 723 | """ | ||
797 | 724 | self.state = 'running' if j.completed else 'testing' | ||
798 | 725 | self.running_t0 = j.running_t0 | ||
799 | 726 | self.test_base_result = j.test_base_result | ||
800 | 727 | self.test_all_result = j.test_all_result | ||
801 | 728 | |||
802 | 729 | def save_json(self): | ||
803 | 730 | """Append the point data to a JSON file for posterity.""" | ||
804 | 731 | # A job must be saved only once. | ||
805 | 732 | if hasattr(self, 'json_saved'): | ||
806 | 733 | log("=== save_json() called more than once. ===", job_id=self.job_id) | ||
807 | 734 | self.json_saved = True | ||
808 | 735 | |||
809 | 736 | path = self.json_path | ||
810 | 737 | state = {} | ||
811 | 738 | |||
812 | 739 | if os.path.exists(path): | ||
813 | 740 | with open(path, 'r') as h: | ||
814 | 741 | state = simplejson.loads(h.read()) | ||
815 | 742 | |||
816 | 743 | value = {} | ||
817 | 744 | committers = [] | ||
818 | 745 | for p in ('addons', 'server', 'web'): | ||
819 | 746 | committers += [ | ||
820 | 747 | p + '_committer_name', | ||
821 | 748 | p + '_committer_xgram', | ||
822 | 749 | # p + '_committer_email', | ||
823 | 750 | ] | ||
824 | 751 | for a in [ | ||
825 | 752 | 'job_id', 'db', 'running_path', 'subdomain', 'server_rev', | ||
826 | 753 | 'addons_rev', 'web_rev', 'need_run_reason', | ||
827 | 754 | 'test_base_result', 'test_all_result', | ||
828 | 755 | ] + committers: | ||
829 | 756 | value[a] = getattr(self, a) | ||
830 | 757 | state.setdefault('jobs', []) | ||
831 | 758 | state['jobs'].append(value) | ||
832 | 759 | |||
833 | 760 | with open(path, 'w') as h: | ||
834 | 761 | data = simplejson.dumps(state, sort_keys=True, indent=4) | ||
835 | 762 | h.write(data) | ||
845 | 763 | 336 | ||
846 | 764 | #---------------------------------------------------------- | 337 | #---------------------------------------------------------- |
847 | 765 | # OpenERP RunBot Engine | 338 | # OpenERP RunBot Engine |
848 | @@ -769,7 +342,7 @@ | |||
849 | 769 | """Used as a singleton, to manage almost all the Runbot state and logic.""" | 342 | """Used as a singleton, to manage almost all the Runbot state and logic.""" |
850 | 770 | def __init__(self, wd, poll, server_net_port, server_xml_port, | 343 | def __init__(self, wd, poll, server_net_port, server_xml_port, |
851 | 771 | client_web_port, number, nginx_port, domain, test, workers, | 344 | client_web_port, number, nginx_port, domain, test, workers, |
853 | 772 | current_job_id, debug, lp): | 345 | current_job_id, debug, lp, default_job_type='install_all'): |
854 | 773 | self.wd=wd | 346 | self.wd=wd |
855 | 774 | self.server_net_port=int(server_net_port) | 347 | self.server_net_port=int(server_net_port) |
856 | 775 | self.server_xml_port=int(server_xml_port) | 348 | self.server_xml_port=int(server_xml_port) |
857 | @@ -788,9 +361,10 @@ | |||
858 | 788 | # realizing a queue of groups to be processed). | 361 | # realizing a queue of groups to be processed). |
859 | 789 | self.current_job_id = current_job_id | 362 | self.current_job_id = current_job_id |
860 | 790 | self.debug = debug | 363 | self.debug = debug |
862 | 791 | self.manual_build_count = 0 | 364 | self.next_build_number = 0 |
863 | 792 | self.launchpad = lp | 365 | self.launchpad = lp |
864 | 793 | self.checked_date_last_modified = None | 366 | self.checked_date_last_modified = None |
865 | 367 | self.default_job_type = default_job_type | ||
866 | 794 | 368 | ||
867 | 795 | def registered_teams(self): | 369 | def registered_teams(self): |
868 | 796 | return openerprunbot.state.get('registered-teams', []) + ['openerp-dev'] | 370 | return openerprunbot.state.get('registered-teams', []) + ['openerp-dev'] |
869 | @@ -833,7 +407,7 @@ | |||
870 | 833 | return gs | 407 | return gs |
871 | 834 | 408 | ||
872 | 835 | def nginx_groups_registered(self,team_name): | 409 | def nginx_groups_registered(self,team_name): |
874 | 836 | gs = [(g.name,g) for g in self.groups.values() if not any(g.points) and g.team_name==team_name and g.manual_build == sys.maxint] | 410 | gs = [(g.name,g) for g in self.groups.values() if not any(g.points) and g.team_name==team_name and g.build_number == sys.maxint] |
875 | 837 | gs.sort() | 411 | gs.sort() |
876 | 838 | return [g for (x,g) in gs] | 412 | return [g for (x,g) in gs] |
877 | 839 | 413 | ||
878 | @@ -897,26 +471,28 @@ | |||
879 | 897 | 471 | ||
880 | 898 | def process_add(self, b, sticky, team_name): | 472 | def process_add(self, b, sticky, team_name): |
881 | 899 | log("runbot-process-add", b.unique_name) | 473 | log("runbot-process-add", b.unique_name) |
883 | 900 | if not re.search("/(openobject|openerp)-(addons|server|client-web|web)/", b.unique_name): | 474 | if not project_name_from_unique_name(b.unique_name): |
884 | 901 | log("WARNING: can't add branch: project name is not standard.", unique_name=b.unique_name) | 475 | log("WARNING: can't add branch: project name is not standard.", unique_name=b.unique_name) |
885 | 902 | return | 476 | return |
886 | 903 | if b.unique_name not in self.branches: | 477 | if b.unique_name not in self.branches: |
888 | 904 | self.branches[b.unique_name] = RunBotBranch(self, b) | 478 | self.branches[b.unique_name] = RunBotBranch(self, b, trigger_build=True) |
889 | 905 | bb = self.branches[b.unique_name] | 479 | bb = self.branches[b.unique_name] |
890 | 906 | if (team_name, bb.name) not in self.groups: | 480 | if (team_name, bb.name) not in self.groups: |
894 | 907 | self.groups[(team_name, bb.name)] = RunBotGroupedBranch(self, bb, team_name, sticky) | 481 | self.groups[(team_name, bb.name)] = RunBotGroupedBranch(self, team_name, bb.name, None, sticky, job_type=self.default_job_type) |
895 | 908 | else: | 482 | self.groups[(team_name, bb.name)].add_branch(bb) |
893 | 909 | self.groups[(team_name, bb.name)].add_branch(bb) | ||
896 | 910 | bb.update_launchpad(b) | 483 | bb.update_launchpad(b) |
897 | 911 | filters = {'status': NEW_MERGE_STATUS} | 484 | filters = {'status': NEW_MERGE_STATUS} |
899 | 912 | bb.merge_count = len(list(b.getMergeProposals(**filters))) | 485 | try: |
900 | 486 | bb.merge_count = len(list(b.getMergeProposals(**filters))) | ||
901 | 487 | except Exception, e: | ||
902 | 488 | bb.merge_count = 0 | ||
903 | 913 | 489 | ||
904 | 914 | def register_configured_branches(self): | 490 | def register_configured_branches(self): |
905 | 915 | for team, v in openerprunbot.state.get('configured-branches', {}).items(): | 491 | for team, v in openerprunbot.state.get('configured-branches', {}).items(): |
906 | 916 | if not team: continue | 492 | if not team: continue |
907 | 917 | for name, c in v.items(): | 493 | for name, c in v.items(): |
908 | 918 | if not name: continue | 494 | if not name: continue |
910 | 919 | g = ConfiguredGroup(self, team, name, c['version'], 0, | 495 | g = RunBotGroupedBranch(self, team, name, c['version'], 0, |
911 | 920 | server_branch=c['server_branch'], | 496 | server_branch=c['server_branch'], |
912 | 921 | client_web_branch=c.get('client_web_branch'), | 497 | client_web_branch=c.get('client_web_branch'), |
913 | 922 | web_branch=c.get('web_branch'), | 498 | web_branch=c.get('web_branch'), |
914 | @@ -925,7 +501,7 @@ | |||
915 | 925 | 501 | ||
916 | 926 | # TODO or if it is already there but with different branches. | 502 | # TODO or if it is already there but with different branches. |
917 | 927 | if (g.team_name, g.name) not in self.groups: | 503 | if (g.team_name, g.name) not in self.groups: |
919 | 928 | g.populate_branches(self.launchpad) | 504 | g.configure_branches(self.launchpad) |
920 | 929 | if g.is_ok(): | 505 | if g.is_ok(): |
921 | 930 | log("adding configured group", team=g.team_name, name=g.name) | 506 | log("adding configured group", team=g.team_name, name=g.name) |
922 | 931 | self.groups[(g.team_name, g.name)] = g | 507 | self.groups[(g.team_name, g.name)] = g |
923 | @@ -936,6 +512,11 @@ | |||
924 | 936 | def populate_branches(self): | 512 | def populate_branches(self): |
925 | 937 | """Return all LP branches matching our teams and projects.""" | 513 | """Return all LP branches matching our teams and projects.""" |
926 | 938 | log("runbot-populate-branches") | 514 | log("runbot-populate-branches") |
927 | 515 | # Get migration branches | ||
928 | 516 | global migration_scripts_branch | ||
929 | 517 | global migration_platform_branch | ||
930 | 518 | migration_scripts_branch = self.launchpad.get_branch(unique_name='~openerp-dev/openerp-int/migration-scripts') | ||
931 | 519 | migration_platform_branch = self.launchpad.get_branch(unique_name='~openerp-dev/openerp-int/migration-platform') | ||
932 | 939 | # Register main sticky branches | 520 | # Register main sticky branches |
933 | 940 | for k, v in MAIN_BRANCHES.items(): | 521 | for k, v in MAIN_BRANCHES.items(): |
934 | 941 | b = self.launchpad.get_branch(unique_name=v) | 522 | b = self.launchpad.get_branch(unique_name=v) |
935 | @@ -944,6 +525,7 @@ | |||
936 | 944 | 525 | ||
937 | 945 | # Register other branches | 526 | # Register other branches |
938 | 946 | for v in openerprunbot.state.get('registered-branches', []): | 527 | for v in openerprunbot.state.get('registered-branches', []): |
939 | 528 | break | ||
940 | 947 | m = openerprunbot.branch_input_re.match(v) | 529 | m = openerprunbot.branch_input_re.match(v) |
941 | 948 | if m: | 530 | if m: |
942 | 949 | b = self.launchpad.get_branch(unique_name=v) | 531 | b = self.launchpad.get_branch(unique_name=v) |
943 | @@ -961,26 +543,19 @@ | |||
944 | 961 | if self.debug: break | 543 | if self.debug: break |
945 | 962 | team_branches = self.launchpad.get_team_branches(team_name) | 544 | team_branches = self.launchpad.get_team_branches(team_name) |
946 | 963 | for b in team_branches: | 545 | for b in team_branches: |
948 | 964 | if re.search("/(openobject|openerp)-(addons|server|client-web|web)/",b.unique_name): | 546 | if project_name_from_unique_name(b.unique_name): |
949 | 965 | self.process_add(b, 0, team_name) | 547 | self.process_add(b, 0, team_name) |
950 | 966 | 548 | ||
951 | 967 | # Register configured branches | 549 | # Register configured branches |
952 | 968 | self.register_configured_branches() | 550 | self.register_configured_branches() |
953 | 969 | 551 | ||
954 | 970 | for g in self.groups.values(): | 552 | for g in self.groups.values(): |
956 | 971 | if not g.is_configured(): | 553 | if not g.configured: |
957 | 972 | g.add_main_branches() | 554 | g.add_main_branches() |
958 | 973 | 555 | ||
959 | 974 | self.checked_date_last_modified = max(x.lp_date_last_modified() for x in self.groups.values()) | 556 | self.checked_date_last_modified = max(x.lp_date_last_modified() for x in self.groups.values()) |
960 | 975 | self.assign_positions() | 557 | self.assign_positions() |
961 | 976 | return self.get_queue() | 558 | return self.get_queue() |
962 | 977 | #return self.sorted_groups() | ||
963 | 978 | |||
964 | 979 | def sorted_groups(self): | ||
965 | 980 | gs = [(g.sticky, -g.manual_build, g.lp_date_last_modified(), g) for g in self.groups.values()] | ||
966 | 981 | gs.sort(reverse=1) | ||
967 | 982 | gs = [g for (x,y,z,g) in gs][:self.number] | ||
968 | 983 | return gs | ||
969 | 984 | 559 | ||
970 | 985 | def assign_positions(self): | 560 | def assign_positions(self): |
971 | 986 | # Sort by last modification date, then assign a position in the queue. | 561 | # Sort by last modification date, then assign a position in the queue. |
972 | @@ -992,13 +567,13 @@ | |||
973 | 992 | continue | 567 | continue |
974 | 993 | if not g.sticky: | 568 | if not g.sticky: |
975 | 994 | self.checked_date_last_modified = g.lp_date_last_modified() | 569 | self.checked_date_last_modified = g.lp_date_last_modified() |
979 | 995 | if g.need_run_reason and g.manual_build == sys.maxint: | 570 | if g.need_run_reason and g.build_number == sys.maxint: |
980 | 996 | self.manual_build_count +=1 | 571 | self.next_build_number +=1 |
981 | 997 | g.manual_build = self.manual_build_count | 572 | g.build_number = self.next_build_number |
982 | 998 | 573 | ||
983 | 999 | def get_queue(self): | 574 | def get_queue(self): |
986 | 1000 | gs = sorted(self.groups.values(), key=lambda x: x.manual_build) | 575 | gs = sorted(self.groups.values(), key=lambda x: x.build_number) |
987 | 1001 | return filter(lambda g: g.manual_build != sys.maxint, gs) | 576 | return filter(lambda g: g.build_number != sys.maxint, gs) |
988 | 1002 | 577 | ||
989 | 1003 | def available_workers(self): | 578 | def available_workers(self): |
990 | 1004 | return self.workers - len([t for t in threading.enumerate() if t.name.startswith('runbot-group-worker-')]) | 579 | return self.workers - len([t for t in threading.enumerate() if t.name.startswith('runbot-group-worker-')]) |
991 | @@ -1008,32 +583,32 @@ | |||
992 | 1008 | command, params = openerprunbot.queue.get() | 583 | command, params = openerprunbot.queue.get() |
993 | 1009 | if command == 'build': | 584 | if command == 'build': |
994 | 1010 | team_name, group_name = params | 585 | team_name, group_name = params |
998 | 1011 | if (team_name, group_name) in self.groups and self.groups[(team_name, group_name)].manual_build == sys.maxint: | 586 | if (team_name, group_name) in self.groups and self.groups[(team_name, group_name)].build_number == sys.maxint: |
999 | 1012 | self.manual_build_count += 1 | 587 | self.next_build_number += 1 |
1000 | 1013 | self.groups[(team_name, group_name)].manual_build = self.manual_build_count | 588 | self.groups[(team_name, group_name)].build_number = self.next_build_number |
1001 | 1014 | self.groups[(team_name, group_name)].need_run_reason.append('build') | 589 | self.groups[(team_name, group_name)].need_run_reason.append('build') |
1002 | 1015 | else: | 590 | else: |
1003 | 1016 | log("WARNING: unknown command", command) | 591 | log("WARNING: unknown command", command) |
1004 | 1017 | 592 | ||
1005 | 1018 | def reset_build_numbers(self): | 593 | def reset_build_numbers(self): |
1007 | 1019 | gs = [(g.manual_build, g) for g in self.groups.values()] | 594 | gs = [(g.build_number, g) for g in self.groups.values()] |
1008 | 1020 | gs.sort() | 595 | gs.sort() |
1009 | 1021 | gs = [g for (x,g) in gs] | 596 | gs = [g for (x,g) in gs] |
1011 | 1022 | self.manual_build_count = 0 | 597 | self.next_build_number = 0 |
1012 | 1023 | sticky_branches = 0 | 598 | sticky_branches = 0 |
1013 | 1024 | for g in gs: | 599 | for g in gs: |
1014 | 1025 | if g.sticky: | 600 | if g.sticky: |
1015 | 1026 | sticky_branches += 1 | 601 | sticky_branches += 1 |
1017 | 1027 | if g.manual_build == sys.maxint: | 602 | if g.build_number == sys.maxint: |
1018 | 1028 | break | 603 | break |
1021 | 1029 | self.manual_build_count += 1 | 604 | self.next_build_number += 1 |
1022 | 1030 | g.manual_build = self.manual_build_count | 605 | g.build_number = self.next_build_number |
1023 | 1031 | self.number = max(sticky_branches + 1, self.number) | 606 | self.number = max(sticky_branches + 1, self.number) |
1024 | 1032 | 607 | ||
1025 | 1033 | def complete_jobs(self): | 608 | def complete_jobs(self): |
1026 | 1034 | """Update all slots with the completed jobs.""" | 609 | """Update all slots with the completed jobs.""" |
1027 | 1035 | for job in self.jobs.values(): | 610 | for job in self.jobs.values(): |
1029 | 1036 | if job.completed: | 611 | if job.state in (STATE_RUNNING, STATE_BROKEN): |
1030 | 1037 | for g in self.groups.values(): | 612 | for g in self.groups.values(): |
1031 | 1038 | if g.name == job.name: | 613 | if g.name == job.name: |
1032 | 1039 | g.complete_point(job) | 614 | g.complete_point(job) |
1033 | @@ -1052,7 +627,7 @@ | |||
1034 | 1052 | for job in self.jobs.itervalues(): | 627 | for job in self.jobs.itervalues(): |
1035 | 1053 | if self.is_sticky_job(job): | 628 | if self.is_sticky_job(job): |
1036 | 1054 | continue | 629 | continue |
1038 | 1055 | if not job.running_t0: | 630 | if not job.running_since: |
1039 | 1056 | continue | 631 | continue |
1040 | 1057 | if not victim or job.job_id < victim.job_id: | 632 | if not victim or job.job_id < victim.job_id: |
1041 | 1058 | victim = job | 633 | victim = job |
1042 | @@ -1082,10 +657,11 @@ | |||
1043 | 1082 | if g.need_run_reason and g.all_points_completed(): | 657 | if g.need_run_reason and g.all_points_completed(): |
1044 | 1083 | port = self.allocate_port() | 658 | port = self.allocate_port() |
1045 | 1084 | self.current_job_id += 1 | 659 | self.current_job_id += 1 |
1047 | 1085 | job = Job(g, port, self.test, self.current_job_id, self.debug) | 660 | job_class = openerprunbot.jobs.JOBS[g.job_type] if g.job_type else openerprunbot.jobs.JOBS[self.default_job_type] |
1048 | 661 | job = job_class(g, port, self.test, self.current_job_id, self.debug) | ||
1049 | 1086 | g.add_point(job) | 662 | g.add_point(job) |
1050 | 1087 | g.need_run_reason = [] | 663 | g.need_run_reason = [] |
1052 | 1088 | g.manual_build = sys.maxint | 664 | g.build_number = sys.maxint |
1053 | 1089 | self.jobs[job.job_id] = job | 665 | self.jobs[job.job_id] = job |
1054 | 1090 | job.spawn() | 666 | job.spawn() |
1055 | 1091 | 667 | ||
1056 | 1092 | 668 | ||
1057 | === added directory 'openerp-runbot/openerprunbot/jobs' | |||
1058 | === added file 'openerp-runbot/openerprunbot/jobs/__init__.py' | |||
1059 | --- openerp-runbot/openerprunbot/jobs/__init__.py 1970-01-01 00:00:00 +0000 | |||
1060 | +++ openerp-runbot/openerprunbot/jobs/__init__.py 2012-07-04 11:46:17 +0000 | |||
1061 | @@ -0,0 +1,7 @@ | |||
1062 | 1 | import install_all | ||
1063 | 2 | import migrate_script | ||
1064 | 3 | |||
1065 | 4 | JOBS = { | ||
1066 | 5 | 'install_all': install_all.InstallAllJob, | ||
1067 | 6 | 'migrate_script': migrate_script.MigrateScriptJob, | ||
1068 | 7 | } | ||
1069 | 0 | 8 | ||
1070 | === added file 'openerp-runbot/openerprunbot/jobs/install_all.py' | |||
1071 | --- openerp-runbot/openerprunbot/jobs/install_all.py 1970-01-01 00:00:00 +0000 | |||
1072 | +++ openerp-runbot/openerprunbot/jobs/install_all.py 2012-07-04 11:46:17 +0000 | |||
1073 | @@ -0,0 +1,366 @@ | |||
1074 | 1 | import os | ||
1075 | 2 | import subprocess | ||
1076 | 3 | import threading | ||
1077 | 4 | import time | ||
1078 | 5 | import traceback | ||
1079 | 6 | |||
1080 | 7 | from ..misc import * | ||
1081 | 8 | |||
1082 | 9 | # Job states | ||
1083 | 10 | STATE_ALLOCATED = 'allocated' | ||
1084 | 11 | STATE_BROKEN = 'broken' | ||
1085 | 12 | STATE_PULLING = 'pulling' | ||
1086 | 13 | STATE_RUNNING = 'running' | ||
1087 | 14 | STATE_TESTING = 'testing' | ||
1088 | 15 | |||
1089 | 16 | class InstallAllJob(object): | ||
1090 | 17 | """ | ||
1091 | 18 | A Job encapsulates all the necessary data to build a branch group for a | ||
1092 | 19 | given slot. The build is done in its own worker thread. | ||
1093 | 20 | """ | ||
1094 | 21 | |||
1095 | 22 | def __init__(self, g, port, test, job_id, debug): | ||
1096 | 23 | self.job_id = job_id | ||
1097 | 24 | self.state = STATE_ALLOCATED | ||
1098 | 25 | self.team_name = g.team_name | ||
1099 | 26 | self.name = g.name | ||
1100 | 27 | self.name_underscore = g.name_underscore | ||
1101 | 28 | self.version = g.version | ||
1102 | 29 | self.debug = debug | ||
1103 | 30 | self.need_run_reason = g.need_run_reason | ||
1104 | 31 | |||
1105 | 32 | self.repo_updates = g.repo_updates() | ||
1106 | 33 | self.port = port | ||
1107 | 34 | self.test = test | ||
1108 | 35 | self.running_server_pid = 0 | ||
1109 | 36 | self.client_web_pid = 0 | ||
1110 | 37 | self.running_since=0 | ||
1111 | 38 | |||
1112 | 39 | repo = os.path.join(g.runbot.wd,'repo') | ||
1113 | 40 | job_suffix = '_job-' + str(self.job_id) | ||
1114 | 41 | self.server_src = g.server.repo_path + job_suffix | ||
1115 | 42 | self.client_web_src = (g.web.repo_path + job_suffix) if g.version == '6.0' and g.web else None | ||
1116 | 43 | self.web_src = (g.web.repo_path + job_suffix) if g.web else None | ||
1117 | 44 | |||
1118 | 45 | # if addons is not the full addons branch use trunk | ||
1119 | 46 | self.addons_src = [a.repo_path + job_suffix for a in g.addons] | ||
1120 | 47 | |||
1121 | 48 | if hasattr(g, 'migration_script'): | ||
1122 | 49 | self.migration_script_src = g.migration_script.repo_path + job_suffix | ||
1123 | 50 | self.migration_platform_src = g.migration_platform.repo_path + job_suffix | ||
1124 | 51 | |||
1125 | 52 | # Running path <Root>/static/<domain> | ||
1126 | 53 | self.subdomain = "%s-%s-%s"%(self.team_name, self.name.replace('_','-').replace('.','-'),self.job_id) | ||
1127 | 54 | self.running_path = os.path.join(g.runbot.wd,'static',self.subdomain) | ||
1128 | 55 | self.json_path = g.json_path | ||
1129 | 56 | self.log_path = os.path.join(self.running_path,'logs') | ||
1130 | 57 | self.flags_path = os.path.join(self.running_path,'flags') | ||
1131 | 58 | |||
1132 | 59 | # Server | ||
1133 | 60 | self.server_path=os.path.join(self.running_path,"server") | ||
1134 | 61 | self.server_bin_path=os.path.join(self.server_path,"openerp-server") | ||
1135 | 62 | self.server_log_path=os.path.join(self.log_path,'server.txt') | ||
1136 | 63 | self.server_log_base_path=os.path.join(self.log_path,'test-base.txt') | ||
1137 | 64 | self.server_log_all_path=os.path.join(self.log_path,'test-all.txt') | ||
1138 | 65 | |||
1139 | 66 | # coverage | ||
1140 | 67 | self.coverage_file_path=os.path.join(self.log_path,'coverage.pickle') | ||
1141 | 68 | self.coverage_base_path=os.path.join(self.log_path,'coverage-base') | ||
1142 | 69 | self.coverage_all_path=os.path.join(self.log_path,'coverage-all') | ||
1143 | 70 | |||
1144 | 71 | # Web60 | ||
1145 | 72 | self.client_web_pid=None | ||
1146 | 73 | self.client_web_path=os.path.join(self.running_path,"client-web") | ||
1147 | 74 | self.client_web_bin_path=os.path.join(self.client_web_path,"openerp-web.py") | ||
1148 | 75 | self.client_web_doc_path=os.path.join(self.client_web_path,"doc") | ||
1149 | 76 | self.client_web_log_path=os.path.join(self.log_path,'client-web.txt') | ||
1150 | 77 | |||
1151 | 78 | # test | ||
1152 | 79 | self.test_base_path=os.path.join(self.log_path,'test-base.txt') | ||
1153 | 80 | self.test_result=None | ||
1154 | 81 | self.test_all_path=os.path.join(self.log_path,'test-all.txt') | ||
1155 | 82 | |||
1156 | 83 | self.server_net_port=g.runbot.server_net_port | ||
1157 | 84 | self.server_xml_port=g.runbot.server_xml_port | ||
1158 | 85 | self.client_web_port=g.runbot.client_web_port | ||
1159 | 86 | |||
1160 | 87 | self.db = self.name_underscore + '_' + str(self.job_id) | ||
1161 | 88 | self.db_all = "%s_all" % self.db | ||
1162 | 89 | |||
1163 | 90 | for p in ('addons', 'server', 'web'): | ||
1164 | 91 | setattr(self, p + '_committer_name', None) | ||
1165 | 92 | setattr(self, p + '_committer_xgram', None) | ||
1166 | 93 | setattr(self, p + '_committer_email', None) | ||
1167 | 94 | self.start_time = 0 | ||
1168 | 95 | self.completed_time = 0 | ||
1169 | 96 | |||
1170 | 97 | self.modules = g.modules | ||
1171 | 98 | |||
1172 | 99 | |||
1173 | 100 | self.server_rev = g.server.local_revision_count | ||
1174 | 101 | self.addons_rev = [a.local_revision_count for a in g.addons] | ||
1175 | 102 | self.web_rev = g.web.local_revision_count if g.web else 0 | ||
1176 | 103 | self.build_number = g.build_number | ||
1177 | 104 | |||
1178 | 105 | def spawn(self): | ||
1179 | 106 | log("runbot-spawn-worker-" + str(self.job_id), group=self.name) | ||
1180 | 107 | t = threading.Thread(target=self.work, name=('runbot-group-worker-' + str(self.job_id))) | ||
1181 | 108 | t.daemon = True | ||
1182 | 109 | t.start() | ||
1183 | 110 | |||
1184 | 111 | def work(self): | ||
1185 | 112 | try: | ||
1186 | 113 | self.state = STATE_PULLING | ||
1187 | 114 | r = self.pull_branches() | ||
1188 | 115 | if r: | ||
1189 | 116 | self.state = STATE_TESTING | ||
1190 | 117 | self.start_time = time.time() | ||
1191 | 118 | self.start() | ||
1192 | 119 | self.completed_time = time.time() | ||
1193 | 120 | self.state = STATE_RUNNING | ||
1194 | 121 | log("runbot-end-worker", job=self.name) | ||
1195 | 122 | else: | ||
1196 | 123 | self.state = STATE_BROKEN | ||
1197 | 124 | log("runbot-end-worker [failed pull]", job=self.name) | ||
1198 | 125 | except Exception, e: | ||
1199 | 126 | self.state = STATE_BROKEN | ||
1200 | 127 | log("runbot-end-worker [with exception]", job=self.name) | ||
1201 | 128 | print traceback.format_exc() | ||
1202 | 129 | |||
1203 | 130 | def pull_branches(self): | ||
1204 | 131 | job_suffix = '_job-' + str(self.job_id) | ||
1205 | 132 | for b in self.repo_updates: | ||
1206 | 133 | log("branch-update",branch=b.unique_name) | ||
1207 | 134 | repo_path = b.repo_path + job_suffix | ||
1208 | 135 | if os.path.exists(repo_path): | ||
1209 | 136 | rc = run(["bzr", "clean-tree", "-d", repo_path, "--force", "--quiet"]) | ||
1210 | 137 | if rc: return False | ||
1211 | 138 | rc = run(["bzr", "pull", "-d", repo_path, "--overwrite", "--quiet"]) | ||
1212 | 139 | if rc: return False | ||
1213 | 140 | else: | ||
1214 | 141 | rc = run(["bzr", "branch", "lp:%s" % b.unique_name, repo_path, "--quiet"]) | ||
1215 | 142 | if rc: return False | ||
1216 | 143 | rc = run(["bzr", "update", "-r", str(b.local_revision_count), repo_path, "--quiet"]) | ||
1217 | 144 | if rc: return False | ||
1218 | 145 | |||
1219 | 146 | committer_name, committer_xgram, committer_email = \ | ||
1220 | 147 | get_committer_info(repo_path) | ||
1221 | 148 | b.committer_name = committer_name | ||
1222 | 149 | b.committer_xgram = committer_xgram | ||
1223 | 150 | b.committer_email = committer_email | ||
1224 | 151 | log("get-commiter-info", name=committer_name, xgram=committer_xgram, email=committer_email) | ||
1225 | 152 | return True | ||
1226 | 153 | |||
1227 | 154 | def start_rsync(self): | ||
1228 | 155 | log("job-start-rsync",branch=self.name) | ||
1229 | 156 | for i in [self.running_path,self.log_path,self.flags_path,self.client_web_doc_path]: | ||
1230 | 157 | if not os.path.exists(i): | ||
1231 | 158 | os.makedirs(i) | ||
1232 | 159 | # copy server | ||
1233 | 160 | run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.server_src, self.server_path]) | ||
1234 | 161 | |||
1235 | 162 | # copy addons (6.0 uses bin/addons, 6.1 and trunk use openerp/addons) | ||
1236 | 163 | addons_dest = "openerp/addons" | ||
1237 | 164 | if not os.path.exists(os.path.join(self.server_path,addons_dest)): | ||
1238 | 165 | addons_dest = "bin/addons" | ||
1239 | 166 | addons_path = os.path.join(self.server_path,addons_dest) | ||
1240 | 167 | for a in self.addons_src: | ||
1241 | 168 | run(["rsync","-a","--exclude",".bzr", "%s/"%a, addons_path]) | ||
1242 | 169 | if self.web_src and self.version != '6.0': | ||
1243 | 170 | run(["rsync","-a","--exclude",".bzr", "%s/addons/"%self.web_src, addons_path]) | ||
1244 | 171 | |||
1245 | 172 | # copy web-client | ||
1246 | 173 | if self.client_web_src: | ||
1247 | 174 | run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.client_web_src, self.client_web_path]) | ||
1248 | 175 | |||
1249 | 176 | def start_createdb(self): | ||
1250 | 177 | run(["psql","template1","-c","select pg_terminate_backend(procpid) from pg_stat_activity where datname in ('%s','%s')"%(self.db,self.db_all)]) | ||
1251 | 178 | time.sleep(3) | ||
1252 | 179 | run(["dropdb",self.db]) | ||
1253 | 180 | run(["dropdb",self.db_all]) | ||
1254 | 181 | run(["createdb",self.db]) | ||
1255 | 182 | run(["createdb",self.db_all]) | ||
1256 | 183 | |||
1257 | 184 | def run_log(self, cmd, logfile, env=None): | ||
1258 | 185 | env = dict(os.environ, **env) if env else None | ||
1259 | 186 | log("run", *cmd, logfile=logfile) | ||
1260 | 187 | out=open(logfile,"w") | ||
1261 | 188 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True, env=env) | ||
1262 | 189 | self.running_server_pid=p.pid | ||
1263 | 190 | p.communicate() | ||
1264 | 191 | return p | ||
1265 | 192 | |||
1266 | 193 | def resolve_server_bin_path(self): | ||
1267 | 194 | # This can be done only if the files are present otherwise the if | ||
1268 | 195 | # will always fail. Alternatively, server_bin_path could be a property. | ||
1269 | 196 | if not os.path.exists(self.server_bin_path): # for 6.0 branches | ||
1270 | 197 | self.server_bin_path=os.path.join(self.server_path,"bin","openerp-server.py") | ||
1271 | 198 | |||
1272 | 199 | def start_test_base(self): | ||
1273 | 200 | log("job-start-server-base") | ||
1274 | 201 | cmd = [self.server_bin_path,"-d",self.db,"-i","base","--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"] | ||
1275 | 202 | _has_test_enable_flag = False | ||
1276 | 203 | if has_test_enable_flag(self.server_bin_path): | ||
1277 | 204 | cmd.append("--test-enable") | ||
1278 | 205 | _has_test_enable_flag = True | ||
1279 | 206 | cmd = ["coverage","run","--branch"] + cmd | ||
1280 | 207 | self.run_log(cmd, logfile=self.test_base_path,env={'COVERAGE_FILE': self.coverage_file_path}) | ||
1281 | 208 | run(["coverage","html","-d",self.coverage_base_path,"--ignore-errors","--include=*.py"],env={'COVERAGE_FILE': self.coverage_file_path}) | ||
1282 | 209 | if _has_test_enable_flag: | ||
1283 | 210 | success_message = "openerp.modules.loading: Modules loaded." | ||
1284 | 211 | rc = not bool(run(["grep",success_message,self.test_base_path])) | ||
1285 | 212 | else: | ||
1286 | 213 | rc = bool(run(["grep","Traceback",self.test_base_path])) | ||
1287 | 214 | return rc | ||
1288 | 215 | |||
1289 | 216 | def start_test_all(self, without_demo=False): | ||
1290 | 217 | log("job-start-server-all") | ||
1291 | 218 | mods = [] | ||
1292 | 219 | if not os.path.exists(os.path.join(self.server_path,"openerp/addons")): | ||
1293 | 220 | mods = os.listdir(os.path.join(self.server_path,"bin/addons")) | ||
1294 | 221 | elif os.path.exists(os.path.join(self.server_path,"openerp/addons")): | ||
1295 | 222 | mods = os.listdir(os.path.join(self.server_path,"openerp/addons")) | ||
1296 | 223 | bl = [ | ||
1297 | 224 | 'document_ftp', 'l10n_lu', | ||
1298 | 225 | '.bzrignore', '__init__.pyc', '__init__.py', 'base_quality_interrogation.py', | ||
1299 | 226 | ] | ||
1300 | 227 | mods = [m for m in mods if m not in bl] | ||
1301 | 228 | if self.modules: | ||
1302 | 229 | # TODO feedback when a requested modules does'nt exist. | ||
1303 | 230 | mods = [m for m in mods if m in self.modules] | ||
1304 | 231 | mods = ",".join(mods) | ||
1305 | 232 | cmd = [self.server_bin_path,"-d",self.db_all,"-i",mods,"--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"] | ||
1306 | 233 | _has_test_enable_flag = False | ||
1307 | 234 | if has_test_enable_flag(self.server_bin_path): | ||
1308 | 235 | cmd.append("--test-enable") | ||
1309 | 236 | _has_test_enable_flag = True | ||
1310 | 237 | if without_demo: | ||
1311 | 238 | cmd.append("--without-demo=True") | ||
1312 | 239 | cmd = ["coverage","run","--branch"] + cmd | ||
1313 | 240 | self.run_log(cmd, logfile=self.test_all_path) | ||
1314 | 241 | run(["coverage","html","-d",self.coverage_all_path,"--ignore-errors","--include=*.py"]) | ||
1315 | 242 | if _has_test_enable_flag: | ||
1316 | 243 | success_message = "openerp.modules.loading: Modules loaded." | ||
1317 | 244 | rc = not bool(run(["grep",success_message,self.test_all_path])) | ||
1318 | 245 | else: | ||
1319 | 246 | rc = bool(run(["grep","Traceback",self.test_all_path])) | ||
1320 | 247 | return rc | ||
1321 | 248 | |||
1322 | 249 | def start_server(self): | ||
1323 | 250 | port = self.port | ||
1324 | 251 | log("job-start-server",branch=self.name,port=port) | ||
1325 | 252 | cmd=[self.server_bin_path,"--no-xmlrpcs","--netrpc-port=%d"%(self.server_net_port+port),"--xmlrpc-port=%d"%(self.server_xml_port+port)] | ||
1326 | 253 | if os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi.py')) \ | ||
1327 | 254 | or os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi', 'core.py')): | ||
1328 | 255 | cmd+=["--db-filter=%d(_.*)?$","--load=web"] | ||
1329 | 256 | log("run",*cmd,log=self.server_log_path) | ||
1330 | 257 | out=open(self.server_log_path,"w") | ||
1331 | 258 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) | ||
1332 | 259 | self.running_server_pid=p.pid | ||
1333 | 260 | |||
1334 | 261 | def start_client_web(self): | ||
1335 | 262 | if not self.client_web_src: | ||
1336 | 263 | return | ||
1337 | 264 | port = self.port | ||
1338 | 265 | log("job-start-client-web",branch=self.name,port=port) | ||
1339 | 266 | config=""" | ||
1340 | 267 | [global] | ||
1341 | 268 | server.environment = "development" | ||
1342 | 269 | server.socket_host = "0.0.0.0" | ||
1343 | 270 | server.socket_port = %d | ||
1344 | 271 | server.thread_pool = 10 | ||
1345 | 272 | tools.sessions.on = True | ||
1346 | 273 | log.access_level = "INFO" | ||
1347 | 274 | log.error_level = "INFO" | ||
1348 | 275 | tools.csrf.on = False | ||
1349 | 276 | tools.log_tracebacks.on = False | ||
1350 | 277 | tools.cgitb.on = True | ||
1351 | 278 | openerp.server.host = 'localhost' | ||
1352 | 279 | openerp.server.port = '%d' | ||
1353 | 280 | openerp.server.protocol = 'socket' | ||
1354 | 281 | openerp.server.timeout = 450 | ||
1355 | 282 | [openerp-web] | ||
1356 | 283 | dblist.filter = 'BOTH' | ||
1357 | 284 | dbbutton.visible = True | ||
1358 | 285 | company.url = '' | ||
1359 | 286 | openerp.server.host = 'localhost' | ||
1360 | 287 | openerp.server.port = '%d' | ||
1361 | 288 | openerp.server.protocol = 'socket' | ||
1362 | 289 | openerp.server.timeout = 450 | ||
1363 | 290 | """%(self.client_web_port+port,self.server_net_port+port,self.server_net_port+port) | ||
1364 | 291 | config=config.replace("\n ","\n") | ||
1365 | 292 | cfgs = [os.path.join(self.client_web_path,"doc","openerp-web.cfg"), os.path.join(self.client_web_path,"openerp-web.cfg")] | ||
1366 | 293 | for i in cfgs: | ||
1367 | 294 | f=open(i,"w") | ||
1368 | 295 | f.write(config) | ||
1369 | 296 | f.close() | ||
1370 | 297 | |||
1371 | 298 | cmd=[self.client_web_bin_path] | ||
1372 | 299 | log("run",*cmd,log=self.client_web_log_path) | ||
1373 | 300 | out=open(self.client_web_log_path,"w") | ||
1374 | 301 | p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) | ||
1375 | 302 | self.client_web_pid=p.pid | ||
1376 | 303 | |||
1377 | 304 | def start(self): | ||
1378 | 305 | log("job-start",branch=self.name,port=self.port) | ||
1379 | 306 | self.start_rsync() | ||
1380 | 307 | self.resolve_server_bin_path() | ||
1381 | 308 | self.start_createdb() | ||
1382 | 309 | try: | ||
1383 | 310 | if self.test: | ||
1384 | 311 | r = self.start_test_base() | ||
1385 | 312 | if not self.debug: | ||
1386 | 313 | self.test_result = r and self.start_test_all() | ||
1387 | 314 | else: | ||
1388 | 315 | self.test_result = r | ||
1389 | 316 | self.start_server() | ||
1390 | 317 | self.start_client_web() | ||
1391 | 318 | except OSError,e: | ||
1392 | 319 | log("branch-start-error",exception=e) | ||
1393 | 320 | except IOError,e: | ||
1394 | 321 | log("branch-start-error",exception=e) | ||
1395 | 322 | self.running_since=time.time() | ||
1396 | 323 | log("branch-started",branch=self.name,port=self.port) | ||
1397 | 324 | |||
1398 | 325 | def stop(self): | ||
1399 | 326 | log("Stopping job", id=self.job_id, branch=self.name) | ||
1400 | 327 | if self.running_server_pid: | ||
1401 | 328 | kill(self.running_server_pid) | ||
1402 | 329 | if self.client_web_pid: | ||
1403 | 330 | kill(self.client_web_pid) | ||
1404 | 331 | |||
1405 | 332 | def save_json(self): | ||
1406 | 333 | """Append the point data to a JSON file for posterity.""" | ||
1407 | 334 | # A job must be saved only once. | ||
1408 | 335 | if hasattr(self, 'json_saved'): | ||
1409 | 336 | log("=== save_json() called more than once. ===", job_id=self.job_id) | ||
1410 | 337 | self.json_saved = True | ||
1411 | 338 | |||
1412 | 339 | path = self.json_path | ||
1413 | 340 | state = {} | ||
1414 | 341 | |||
1415 | 342 | if os.path.exists(path): | ||
1416 | 343 | with open(path, 'r') as h: | ||
1417 | 344 | state = simplejson.loads(h.read()) | ||
1418 | 345 | |||
1419 | 346 | value = {} | ||
1420 | 347 | committers = [] | ||
1421 | 348 | for p in ('addons', 'server', 'web'): | ||
1422 | 349 | committers += [ | ||
1423 | 350 | p + '_committer_name', | ||
1424 | 351 | p + '_committer_xgram', | ||
1425 | 352 | # p + '_committer_email', | ||
1426 | 353 | ] | ||
1427 | 354 | for a in [ | ||
1428 | 355 | 'job_id', 'db', 'running_path', 'subdomain', 'server_rev', | ||
1429 | 356 | 'addons_rev', 'web_rev', 'need_run_reason', | ||
1430 | 357 | 'test_result', | ||
1431 | 358 | ] + committers: | ||
1432 | 359 | value[a] = getattr(self, a) | ||
1433 | 360 | state.setdefault('jobs', []) | ||
1434 | 361 | state['jobs'].append(value) | ||
1435 | 362 | |||
1436 | 363 | with open(path, 'w') as h: | ||
1437 | 364 | data = simplejson.dumps(state, sort_keys=True, indent=4) | ||
1438 | 365 | h.write(data) | ||
1439 | 366 | |||
1440 | 0 | 367 | ||
1441 | === added file 'openerp-runbot/openerprunbot/jobs/migrate_script.py' | |||
1442 | --- openerp-runbot/openerprunbot/jobs/migrate_script.py 1970-01-01 00:00:00 +0000 | |||
1443 | +++ openerp-runbot/openerprunbot/jobs/migrate_script.py 2012-07-04 11:46:17 +0000 | |||
1444 | @@ -0,0 +1,61 @@ | |||
1445 | 1 | import os | ||
1446 | 2 | import subprocess | ||
1447 | 3 | import threading | ||
1448 | 4 | import time | ||
1449 | 5 | import traceback | ||
1450 | 6 | |||
1451 | 7 | from install_all import InstallAllJob | ||
1452 | 8 | from ..misc import * | ||
1453 | 9 | |||
1454 | 10 | # Job states | ||
1455 | 11 | STATE_ALLOCATED = 'allocated' | ||
1456 | 12 | STATE_BROKEN = 'broken' | ||
1457 | 13 | STATE_PULLING = 'pulling' | ||
1458 | 14 | STATE_RUNNING = 'running' | ||
1459 | 15 | STATE_TESTING = 'testing' | ||
1460 | 16 | |||
1461 | 17 | class MigrateScriptJob(InstallAllJob): | ||
1462 | 18 | |||
1463 | 19 | def start(self, migrate=False): | ||
1464 | 20 | log("job-start",branch=self.name,port=self.port) | ||
1465 | 21 | self.migration_log_path=os.path.join(self.log_path,'migration.txt') | ||
1466 | 22 | |||
1467 | 23 | self.start_rsync() | ||
1468 | 24 | self.resolve_server_bin_path() | ||
1469 | 25 | self.start_createdb() | ||
1470 | 26 | try: | ||
1471 | 27 | rc0 = self.start_test_all(without_demo=True) | ||
1472 | 28 | |||
1473 | 29 | # Run the migration towards trunk | ||
1474 | 30 | if False: # if migrate | ||
1475 | 31 | run(['rm', '-rf', 'migration-' + str(self.job_id)]) | ||
1476 | 32 | run(['cp', '-r', self.migration_script_src, 'migration-' + str(self.job_id)]) | ||
1477 | 33 | run(['cp', self.migration_platform_src + '/Makefile', 'migration-' + str(self.job_id)]) | ||
1478 | 34 | cmd = 'make pre update post updatelog reset-demoflag'.split() + [ | ||
1479 | 35 | 'OPENERP_BIN=' + self.server_bin_path, | ||
1480 | 36 | 'DATABASE=' + self.db_all, | ||
1481 | 37 | 'VERSION=trunk'] | ||
1482 | 38 | run_output(cmd, cwd='migration-' + str(self.job_id)) | ||
1483 | 39 | |||
1484 | 40 | # Check the migrated database with an 'update all' and tests enabled. | ||
1485 | 41 | cmd = ['psql', self.db_all, '-c', "update ir_module_module set demo='t'"] | ||
1486 | 42 | run(cmd) | ||
1487 | 43 | cmd = [self.server_bin_path, "-d", self.db_all, "-u", "base", "--stop-after-init", "--no-xmlrpc", "--no-xmlrpcs", "--no-netrpc", "--log-level=test"] | ||
1488 | 44 | _has_test_enable_flag = False | ||
1489 | 45 | if has_test_enable_flag(self.server_bin_path): | ||
1490 | 46 | cmd.append("--test-enable") | ||
1491 | 47 | _has_test_enable_flag = True | ||
1492 | 48 | self.run_log(cmd, logfile=self.test_all_path) | ||
1493 | 49 | if _has_test_enable_flag: | ||
1494 | 50 | success_message = "openerp.modules.loading: Modules loaded." | ||
1495 | 51 | rc = not bool(run(["grep",success_message,self.test_all_path])) | ||
1496 | 52 | else: | ||
1497 | 53 | rc = bool(run(["grep","Traceback",self.test_all_path])) | ||
1498 | 54 | self.test_result = rc0 and rc | ||
1499 | 55 | |||
1500 | 56 | except OSError,e: | ||
1501 | 57 | log("branch-start-error",exception=e) | ||
1502 | 58 | except IOError,e: | ||
1503 | 59 | log("branch-start-error",exception=e) | ||
1504 | 60 | self.running_since=time.time() | ||
1505 | 61 | |||
1506 | 0 | 62 | ||
1507 | === modified file 'openerp-runbot/openerprunbot/misc.py' | |||
1508 | --- openerp-runbot/openerprunbot/misc.py 2011-11-24 10:09:50 +0000 | |||
1509 | +++ openerp-runbot/openerprunbot/misc.py 2012-07-04 11:46:17 +0000 | |||
1510 | @@ -3,6 +3,7 @@ | |||
1511 | 3 | """ | 3 | """ |
1512 | 4 | 4 | ||
1513 | 5 | import fcntl | 5 | import fcntl |
1514 | 6 | import re | ||
1515 | 6 | import signal | 7 | import signal |
1516 | 7 | import subprocess | 8 | import subprocess |
1517 | 8 | import threading | 9 | import threading |
1518 | @@ -10,7 +11,7 @@ | |||
1519 | 10 | import os | 11 | import os |
1520 | 11 | 12 | ||
1521 | 12 | __all__ = [ | 13 | __all__ = [ |
1523 | 13 | 'kill', 'log', 'run', 'run_output' | 14 | 'kill', 'log', 'run', 'run_output', 'get_committer_info', 'underscore', 'has_test_enable_flag', 'project_name_from_unique_name' |
1524 | 14 | ] | 15 | ] |
1525 | 15 | 16 | ||
1526 | 16 | log_lock = threading.Lock() | 17 | log_lock = threading.Lock() |
1527 | @@ -62,4 +63,56 @@ | |||
1528 | 62 | pass | 63 | pass |
1529 | 63 | 64 | ||
1530 | 64 | def run_output(l, cwd=None): | 65 | def run_output(l, cwd=None): |
1531 | 66 | log("run_output",l) | ||
1532 | 65 | return subprocess.Popen(l, stdout=subprocess.PIPE, cwd=cwd).communicate()[0] | 67 | return subprocess.Popen(l, stdout=subprocess.PIPE, cwd=cwd).communicate()[0] |
1533 | 68 | |||
1534 | 69 | #---------------------------------------------------------- | ||
1535 | 70 | # OpenERP RunBot misc | ||
1536 | 71 | #---------------------------------------------------------- | ||
1537 | 72 | |||
1538 | 73 | def underscore(s): | ||
1539 | 74 | return s.replace("~","").replace(":","_").replace("/","_").replace(".","_").replace(" ","_") | ||
1540 | 75 | |||
1541 | 76 | def get_committer_info(repo_path): | ||
1542 | 77 | committer_name = None | ||
1543 | 78 | committer_xgram = None | ||
1544 | 79 | committer_email = None | ||
1545 | 80 | |||
1546 | 81 | output = run_output(["bzr", "log", "--long", "-r-1"], cwd=repo_path) | ||
1547 | 82 | |||
1548 | 83 | committer_re = re.compile('committer: *(.+)<(.+)@(.+)>') | ||
1549 | 84 | for i in output.split('\n'): | ||
1550 | 85 | m = committer_re.match(i) | ||
1551 | 86 | if m: | ||
1552 | 87 | committer_name = m.group(1).strip() | ||
1553 | 88 | committer_xgram = m.group(2) | ||
1554 | 89 | committer_email = m.group(2) + '@' + m.group(3) | ||
1555 | 90 | break | ||
1556 | 91 | |||
1557 | 92 | return committer_name, committer_xgram, committer_email | ||
1558 | 93 | |||
1559 | 94 | def has_test_enable_flag(server_bin_path): | ||
1560 | 95 | """ | ||
1561 | 96 | Test whether an openerp-server executable has the --test-enable flag. | ||
1562 | 97 | (When the flag is present, testing for success/failure is done in a | ||
1563 | 98 | different way.) | ||
1564 | 99 | """ | ||
1565 | 100 | p1 = subprocess.Popen([server_bin_path, "--help"], | ||
1566 | 101 | stdout=subprocess.PIPE) | ||
1567 | 102 | p2 = subprocess.Popen(["grep", "test-enable"], stdin=p1.stdout, | ||
1568 | 103 | stdout=subprocess.PIPE) | ||
1569 | 104 | p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits. | ||
1570 | 105 | output = p2.communicate()[0] | ||
1571 | 106 | return output == " --test-enable Enable YAML and unit tests.\n" | ||
1572 | 107 | |||
1573 | 108 | def project_name_from_unique_name(unique_name): | ||
1574 | 109 | m = re.search("/(openobject|openerp)-(addons|server|client-web|web)/", unique_name) | ||
1575 | 110 | if m: | ||
1576 | 111 | n = m.group(2) | ||
1577 | 112 | if n == 'client-web': | ||
1578 | 113 | return 'web' | ||
1579 | 114 | else: | ||
1580 | 115 | return n | ||
1581 | 116 | else: | ||
1582 | 117 | return None | ||
1583 | 118 | |||
1584 | 66 | 119 | ||
1585 | === modified file 'openerp-runbot/openerprunbot/templates/branches.html.mako' | |||
1586 | --- openerp-runbot/openerprunbot/templates/branches.html.mako 2012-05-23 13:07:13 +0000 | |||
1587 | +++ openerp-runbot/openerprunbot/templates/branches.html.mako 2012-07-04 11:46:17 +0000 | |||
1588 | @@ -106,11 +106,12 @@ | |||
1589 | 106 | <h2>Runtime data</h2> | 106 | <h2>Runtime data</h2> |
1590 | 107 | <div class="stats"> | 107 | <div class="stats"> |
1591 | 108 | (The following data are global to the Runbot.)<br /> | 108 | (The following data are global to the Runbot.)<br /> |
1592 | 109 | Default job type: ${r.default_job_type}.<br /> | ||
1593 | 109 | Maximum ${r.number} concurrent branches.<br /> | 110 | Maximum ${r.number} concurrent branches.<br /> |
1594 | 110 | Maximum ${r.workers} concurrent jobs.<br /> | 111 | Maximum ${r.workers} concurrent jobs.<br /> |
1595 | 111 | ${r.current_job_id} processed jobs.<br /> | 112 | ${r.current_job_id} processed jobs.<br /> |
1596 | 112 | ${r.workers - r.available_workers()} ongoing jobs.<br /> | 113 | ${r.workers - r.available_workers()} ongoing jobs.<br /> |
1598 | 113 | ${r.manual_build_count} manual build requests to go.<br /> | 114 | ${r.next_build_number} manual build requests to go.<br /> |
1599 | 114 | </div> | 115 | </div> |
1600 | 115 | </div> | 116 | </div> |
1601 | 116 | 117 | ||
1602 | 117 | 118 | ||
1603 | === modified file 'openerp-runbot/openerprunbot/templates/defs.html.mako' | |||
1604 | --- openerp-runbot/openerprunbot/templates/defs.html.mako 2012-06-21 10:24:31 +0000 | |||
1605 | +++ openerp-runbot/openerprunbot/templates/defs.html.mako 2012-07-04 11:46:17 +0000 | |||
1606 | @@ -14,9 +14,9 @@ | |||
1607 | 14 | <span class="i action-toggle dropdown-toggle" data-toggle="dropdown">B</span> | 14 | <span class="i action-toggle dropdown-toggle" data-toggle="dropdown">B</span> |
1608 | 15 | <ul class="dropdown-menu"> | 15 | <ul class="dropdown-menu"> |
1609 | 16 | <li><a href="http://${r.domain}/${p.subdomain}/logs/test-all.txt"> | 16 | <li><a href="http://${r.domain}/${p.subdomain}/logs/test-all.txt"> |
1611 | 17 | % if p.test_all_result==False: | 17 | % if p.test_result==False: |
1612 | 18 | Install logs (failure) | 18 | Install logs (failure) |
1614 | 19 | % elif p.test_all_result==True: | 19 | % elif p.test_result==True: |
1615 | 20 | Install logs (success) | 20 | Install logs (success) |
1616 | 21 | % else: | 21 | % else: |
1617 | 22 | Install logs (ongoing) | 22 | Install logs (ongoing) |
1618 | @@ -30,25 +30,31 @@ | |||
1619 | 30 | % if 'build' in p.need_run_reason: | 30 | % if 'build' in p.need_run_reason: |
1620 | 31 | <li>(Build manually requested)</li> | 31 | <li>(Build manually requested)</li> |
1621 | 32 | % endif | 32 | % endif |
1624 | 33 | % if p.manual_build != sys.maxint: | 33 | % if p.build_number != sys.maxint: |
1625 | 34 | <li>(${p.manual_build})</li> | 34 | <li>(${p.build_number})</li> |
1626 | 35 | % endif | 35 | % endif |
1627 | 36 | </ul> | 36 | </ul> |
1628 | 37 | % if p.state == 'testing': | 37 | % if p.state == 'testing': |
1629 | 38 | <p><span class="status"> </span> | 38 | <p><span class="status"> </span> |
1631 | 39 | % elif p.test_base_result==True and p.test_all_result==True: | 39 | % elif p.test_result==True: |
1632 | 40 | <p><span class="status green"> </span> | 40 | <p><span class="status green"> </span> |
1634 | 41 | % elif p.test_base_result==False or p.test_all_result==False: | 41 | % elif p.test_result==False: |
1635 | 42 | <p><span class="status red"> </span> | 42 | <p><span class="status red"> </span> |
1636 | 43 | % else: | 43 | % else: |
1637 | 44 | <p><span class="status"> </span> | 44 | <p><span class="status"> </span> |
1638 | 45 | % endif | 45 | % endif |
1639 | 46 | % if p.state == 'testing': | 46 | % if p.state == 'testing': |
1640 | 47 | <span class="testing">Testing...</span></p> | 47 | <span class="testing">Testing...</span></p> |
1645 | 48 | % elif p.running_t0 and p.test_all_result: | 48 | % elif p.state == 'pulling': |
1646 | 49 | <span class="running-long">Age: ${r.nginx_index_time(t-p.running_t0)}</span></p> | 49 | <span class="testing">Pulling...</span></p> |
1647 | 50 | % elif p.running_t0: | 50 | % elif p.state == 'allocated': |
1648 | 51 | <span class="testing">Age: ${r.nginx_index_time(t-p.running_t0)}</span></p> | 51 | <span class="testing">Allocated...</span></p> |
1649 | 52 | % elif p.state == 'broken': | ||
1650 | 53 | <span class="testing">Internal error</span></p> | ||
1651 | 54 | % elif p.running_since and p.test_result: | ||
1652 | 55 | <span class="running-long">Age: ${r.nginx_index_time(t-p.running_since)}</span></p> | ||
1653 | 56 | % elif p.running_since: | ||
1654 | 57 | <span class="testing">Age: ${r.nginx_index_time(t-p.running_since)}</span></p> | ||
1655 | 52 | % else: | 58 | % else: |
1656 | 53 | <span class="testing">Internal error</span></p> | 59 | <span class="testing">Internal error</span></p> |
1657 | 54 | % endif | 60 | % endif |
1658 | @@ -58,7 +64,7 @@ | |||
1659 | 58 | ${rev_(r,g,b,p)} | 64 | ${rev_(r,g,b,p)} |
1660 | 59 | % endfor | 65 | % endfor |
1661 | 60 | </ul> | 66 | </ul> |
1663 | 61 | % if ((p.job_id > r.current_job_id - r.number) and p.running_t0) or g.is_sticky_job(p): | 67 | % if ((p.job_id > r.current_job_id - r.number) or g.is_sticky_job(p)) and p.running_since: |
1664 | 62 | <form target="_blank" method="GET" action="http://${p.db}.${r.domain}/"> | 68 | <form target="_blank" method="GET" action="http://${p.db}.${r.domain}/"> |
1665 | 63 | <button type="submit">Connect</button> | 69 | <button type="submit">Connect</button> |
1666 | 64 | </form> | 70 | </form> |
1667 | @@ -69,7 +75,7 @@ | |||
1668 | 69 | </%def> | 75 | </%def> |
1669 | 70 | 76 | ||
1670 | 71 | <%def name="branch_(r,g,b)"> | 77 | <%def name="branch_(r,g,b)"> |
1672 | 72 | % if b.overriden_repo_path: | 78 | % if not b.trigger_build: |
1673 | 73 | <span class="label"> | 79 | <span class="label"> |
1674 | 74 | % else: | 80 | % else: |
1675 | 75 | <span class="label notice"> | 81 | <span class="label notice"> |
1676 | @@ -97,8 +103,8 @@ | |||
1677 | 97 | % if g.wrong_matching: | 103 | % if g.wrong_matching: |
1678 | 98 | <span class="wrong-matching">These branches are not correctly named (<a href="/#how">see the instructions</a>).</span> | 104 | <span class="wrong-matching">These branches are not correctly named (<a href="/#how">see the instructions</a>).</span> |
1679 | 99 | % endif | 105 | % endif |
1682 | 100 | % if g.manual_build != sys.maxint: | 106 | % if g.build_number != sys.maxint: |
1683 | 101 | <p><span>(build n.${g.manual_build})</span></p> | 107 | <p><span>(build n.${g.build_number})</span></p> |
1684 | 102 | % elif not g.sticky: | 108 | % elif not g.sticky: |
1685 | 103 | ${build_button_(r, g)} | 109 | ${build_button_(r, g)} |
1686 | 104 | % endif | 110 | % endif |
1687 | @@ -126,16 +132,16 @@ | |||
1688 | 126 | <tr> | 132 | <tr> |
1689 | 127 | <td> | 133 | <td> |
1690 | 128 | <form method="POST" action="http://${r.domain}/a?build=${g.name}&team=${g.team_name}"> | 134 | <form method="POST" action="http://${r.domain}/a?build=${g.name}&team=${g.team_name}"> |
1693 | 129 | % if g.manual_build != sys.maxint: | 135 | % if g.build_number != sys.maxint: |
1694 | 130 | <strong>${g.manual_build}.</strong> | 136 | <strong>${g.build_number}.</strong> |
1695 | 131 | % endif | 137 | % endif |
1696 | 132 | ${g.name} | 138 | ${g.name} |
1697 | 133 | <span>(${g.team_name})</span> | 139 | <span>(${g.team_name})</span> |
1699 | 134 | % if g.manual_build == sys.maxint: | 140 | % if g.build_number == sys.maxint: |
1700 | 135 | <button type="submit">Force Build</button> | 141 | <button type="submit">Force Build</button> |
1701 | 136 | % endif | 142 | % endif |
1702 | 137 | % for b in g.repo_updates(): | 143 | % for b in g.repo_updates(): |
1704 | 138 | % if not b.overriden_repo_path: | 144 | % if b.trigger_build: |
1705 | 139 | ${branch_(r,g,b)} | 145 | ${branch_(r,g,b)} |
1706 | 140 | % endif | 146 | % endif |
1707 | 141 | % endfor | 147 | % endfor |
1708 | 142 | 148 | ||
1709 | === modified file 'openerp-runbot/openerprunbot/templates/nginx.conf.mako' | |||
1710 | --- openerp-runbot/openerprunbot/templates/nginx.conf.mako 2012-04-27 10:25:23 +0000 | |||
1711 | +++ openerp-runbot/openerprunbot/templates/nginx.conf.mako 2012-07-04 11:46:17 +0000 | |||
1712 | @@ -10,7 +10,7 @@ | |||
1713 | 10 | client_body_temp_path nginx; proxy_temp_path nginx; fastcgi_temp_path nginx; access_log nginx/access.log; index index.html; | 10 | client_body_temp_path nginx; proxy_temp_path nginx; fastcgi_temp_path nginx; access_log nginx/access.log; index index.html; |
1714 | 11 | % for g in r.groups.values(): | 11 | % for g in r.groups.values(): |
1715 | 12 | % for i in g.points: | 12 | % for i in g.points: |
1717 | 13 | % if i and (i.job_id > r.current_job_id - r.number) and i.running_t0: | 13 | % if i and (i.job_id > r.current_job_id - r.number or g.is_sticky_job(i)) and i.running_since: |
1718 | 14 | % if i.team_name == 'openerp-dev' and g.is_sticky_job(i): | 14 | % if i.team_name == 'openerp-dev' and g.is_sticky_job(i): |
1719 | 15 | server { # openerp-dev sticky job (i.e. first job of a sticky branch) | 15 | server { # openerp-dev sticky job (i.e. first job of a sticky branch) |
1720 | 16 | listen ${r.nginx_port}; | 16 | listen ${r.nginx_port}; |
1721 | 17 | 17 | ||
1722 | === added file 'openerp-runbot/starting.html' | |||
1723 | --- openerp-runbot/starting.html 1970-01-01 00:00:00 +0000 | |||
1724 | +++ openerp-runbot/starting.html 2012-07-04 11:46:17 +0000 | |||
1725 | @@ -0,0 +1,1 @@ | |||
1726 | 1 | The runbot is starting over. | ||
1727 | 0 | 2 | ||
1728 | === added file 'openerp-runbot/stopped.html' | |||
1729 | --- openerp-runbot/stopped.html 1970-01-01 00:00:00 +0000 | |||
1730 | +++ openerp-runbot/stopped.html 2012-07-04 11:46:17 +0000 | |||
1731 | @@ -0,0 +1,1 @@ | |||
1732 | 1 | The runbot is down for "planned" maintenance. | ||
1733 | 0 | 2 | ||
1734 | === modified file 'openerp-runbot/try-template.py' | |||
1735 | --- openerp-runbot/try-template.py 2012-04-18 13:00:01 +0000 | |||
1736 | +++ openerp-runbot/try-template.py 2012-07-04 11:46:17 +0000 | |||
1737 | @@ -12,8 +12,8 @@ | |||
1738 | 12 | unique_name = '~openerp-dev/openobject-server/trunk-dummy' | 12 | unique_name = '~openerp-dev/openobject-server/trunk-dummy' |
1739 | 13 | merge_count = 1 | 13 | merge_count = 1 |
1740 | 14 | local_revision_count = 333 | 14 | local_revision_count = 333 |
1743 | 15 | overriden_repo_path = None | 15 | repo_path = None |
1744 | 16 | overriden_project_name = None | 16 | project_name = None |
1745 | 17 | 17 | ||
1746 | 18 | class Point(object): | 18 | class Point(object): |
1747 | 19 | """ Dummy Point class to test mako templates. """ | 19 | """ Dummy Point class to test mako templates. """ |
1748 | @@ -24,8 +24,8 @@ | |||
1749 | 24 | subdomain = 'trunk_dummy_2' | 24 | subdomain = 'trunk_dummy_2' |
1750 | 25 | port = 22 | 25 | port = 22 |
1751 | 26 | need_run_reason = ['server'] | 26 | need_run_reason = ['server'] |
1754 | 27 | manual_build = sys.maxint | 27 | build_number = sys.maxint |
1755 | 28 | running_t0 = 100000 | 28 | running_since = 100000 |
1756 | 29 | repo_updates = [RunBotBranch()] | 29 | repo_updates = [RunBotBranch()] |
1757 | 30 | version = 'trunk' | 30 | version = 'trunk' |
1758 | 31 | 31 | ||
1759 | @@ -33,7 +33,7 @@ | |||
1760 | 33 | """ Dummy Group class to test mako templates. """ | 33 | """ Dummy Group class to test mako templates. """ |
1761 | 34 | name = 'trunk-dummy' | 34 | name = 'trunk-dummy' |
1762 | 35 | team_name = 'openerp-dev' | 35 | team_name = 'openerp-dev' |
1764 | 36 | manual_build = sys.maxint | 36 | build_number = sys.maxint |
1765 | 37 | wrong_matching = False | 37 | wrong_matching = False |
1766 | 38 | points = [None, None, Point()] | 38 | points = [None, None, Point()] |
1767 | 39 | version = 'trunk' | 39 | version = 'trunk' |
1768 | @@ -46,7 +46,7 @@ | |||
1769 | 46 | number = 555 | 46 | number = 555 |
1770 | 47 | workers = 666 | 47 | workers = 666 |
1771 | 48 | current_job_id = 777 | 48 | current_job_id = 777 |
1773 | 49 | manual_build_count = 888 | 49 | next_build_number = 888 |
1774 | 50 | server_net_port = 12000 | 50 | server_net_port = 12000 |
1775 | 51 | server_xml_port = 12100 | 51 | server_xml_port = 12100 |
1776 | 52 | def nginx_index_time(self, t): | 52 | def nginx_index_time(self, t): |