... |
... |
@@ -20,15 +20,19 @@ Operations command |
20
|
20
|
Check the status of operations
|
21
|
21
|
"""
|
22
|
22
|
|
|
23
|
+from collections import OrderedDict
|
23
|
24
|
import logging
|
|
25
|
+from operator import attrgetter
|
24
|
26
|
from urllib.parse import urlparse
|
25
|
27
|
import sys
|
26
|
28
|
|
27
|
29
|
import click
|
|
30
|
+from google.protobuf import json_format
|
28
|
31
|
import grpc
|
29
|
32
|
|
30
|
33
|
from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
|
31
|
34
|
from buildgrid._protos.google.longrunning import operations_pb2, operations_pb2_grpc
|
|
35
|
+from buildgrid._protos.google.rpc import code_pb2
|
32
|
36
|
|
33
|
37
|
from ..cli import pass_context
|
34
|
38
|
|
... |
... |
@@ -65,45 +69,149 @@ def cli(context, remote, instance_name, client_key, client_cert, server_cert): |
65
|
69
|
context.logger.debug("Starting for remote {}".format(context.remote))
|
66
|
70
|
|
67
|
71
|
|
|
72
|
+def _print_operation_status(operation, metadata=None, print_details=False):
|
|
73
|
+ if metadata is None:
|
|
74
|
+ metadata = remote_execution_pb2.ExecuteOperationMetadata()
|
|
75
|
+ # The metadata is expected to be an ExecuteOperationMetadata message:
|
|
76
|
+ assert operation.metadata.Is(metadata.DESCRIPTOR)
|
|
77
|
+ operation.metadata.Unpack(metadata)
|
|
78
|
+
|
|
79
|
+ stage_name = remote_execution_pb2.ExecuteOperationMetadata.Stage.Name(
|
|
80
|
+ metadata.stage).upper()
|
|
81
|
+
|
|
82
|
+ if not operation.done:
|
|
83
|
+ if stage_name == 'CACHE_CHECK':
|
|
84
|
+ click.echo('CacheCheck: {}: Querying action-cache (stage={})'
|
|
85
|
+ .format(operation.name, metadata.stage))
|
|
86
|
+ elif stage_name == 'QUEUED':
|
|
87
|
+ click.echo('Queued: {}: Waiting for execution (stage={})'
|
|
88
|
+ .format(operation.name, metadata.stage))
|
|
89
|
+ elif stage_name == 'EXECUTING':
|
|
90
|
+ click.echo('Executing: {}: Currently running (stage={})'
|
|
91
|
+ .format(operation.name, metadata.stage))
|
|
92
|
+ else:
|
|
93
|
+ click.echo('Error: {}: In an invalid state (stage={})'
|
|
94
|
+ .format(operation.name, metadata.stage), err=True)
|
|
95
|
+
|
|
96
|
+ return
|
|
97
|
+
|
|
98
|
+ assert stage_name == 'COMPLETED'
|
|
99
|
+
|
|
100
|
+ response = remote_execution_pb2.ExecuteResponse()
|
|
101
|
+ # The response is expected to be an ExecutionResponse message:
|
|
102
|
+ assert operation.response.Is(response.DESCRIPTOR)
|
|
103
|
+ operation.response.Unpack(response)
|
|
104
|
+
|
|
105
|
+ if response.status.code != code_pb2.OK:
|
|
106
|
+ click.echo('Failure: {}: {} (code={})'
|
|
107
|
+ .format(operation.name, response.status.message, response.status.code))
|
|
108
|
+ else:
|
|
109
|
+ if response.result.exit_code != 0:
|
|
110
|
+ click.echo('Success: {}: Completed with failure (stage={}, exit_code={})'
|
|
111
|
+ .format(operation.name, metadata.stage, response.result.exit_code))
|
|
112
|
+ else:
|
|
113
|
+ click.echo('Success: {}: Completed succesfully (stage={}, exit_code={})'
|
|
114
|
+ .format(operation.name, metadata.stage, response.result.exit_code))
|
|
115
|
+
|
|
116
|
+ if print_details:
|
|
117
|
+ metadata = response.result.execution_metadata
|
|
118
|
+ click.echo(' worker={}'.format(metadata.worker))
|
|
119
|
+
|
|
120
|
+ queued = metadata.queued_timestamp.ToDatetime()
|
|
121
|
+ click.echo(' queued_at={}'.format(queued))
|
|
122
|
+
|
|
123
|
+ worker_start = metadata.worker_start_timestamp.ToDatetime()
|
|
124
|
+ worker_completed = metadata.worker_completed_timestamp.ToDatetime()
|
|
125
|
+ click.echo(' work_duration={}'.format(worker_completed - worker_start))
|
|
126
|
+
|
|
127
|
+ fetch_start = metadata.input_fetch_start_timestamp.ToDatetime()
|
|
128
|
+ fetch_completed = metadata.input_fetch_completed_timestamp.ToDatetime()
|
|
129
|
+ click.echo(' fetch_duration={}'.format(fetch_completed - fetch_start))
|
|
130
|
+
|
|
131
|
+ execution_start = metadata.execution_start_timestamp.ToDatetime()
|
|
132
|
+ execution_completed = metadata.execution_completed_timestamp.ToDatetime()
|
|
133
|
+ click.echo(' exection_duration={}'.format(execution_completed - execution_start))
|
|
134
|
+
|
|
135
|
+ upload_start = metadata.output_upload_start_timestamp.ToDatetime()
|
|
136
|
+ upload_completed = metadata.output_upload_completed_timestamp.ToDatetime()
|
|
137
|
+ click.echo(' upload_duration={}'.format(upload_completed - upload_start))
|
|
138
|
+
|
|
139
|
+ click.echo(' total_duration={}'.format(worker_completed - queued))
|
|
140
|
+
|
|
141
|
+
|
68
|
142
|
@cli.command('status', short_help="Get the status of an operation.")
|
69
|
143
|
@click.argument('operation-name', nargs=1, type=click.STRING, required=True)
|
|
144
|
+@click.option('--json', is_flag=True, show_default=True,
|
|
145
|
+ help="Print operations status in JSON format.")
|
70
|
146
|
@pass_context
|
71
|
|
-def status(context, operation_name):
|
72
|
|
- context.logger.info("Getting operation status...")
|
|
147
|
+def status(context, operation_name, json):
|
73
|
148
|
stub = operations_pb2_grpc.OperationsStub(context.channel)
|
74
|
|
-
|
75
|
149
|
request = operations_pb2.GetOperationRequest(name=operation_name)
|
76
|
150
|
|
77
|
|
- response = stub.GetOperation(request)
|
78
|
|
- context.logger.info(response)
|
|
151
|
+ operation = stub.GetOperation(request)
|
|
152
|
+
|
|
153
|
+ if not json:
|
|
154
|
+ _print_operation_status(operation, print_details=True)
|
|
155
|
+ else:
|
|
156
|
+ click.echo(json_format.MessageToJson(operation))
|
79
|
157
|
|
80
|
158
|
|
81
|
159
|
@cli.command('list', short_help="List operations.")
|
|
160
|
+@click.option('--json', is_flag=True, show_default=True,
|
|
161
|
+ help="Print operations list in JSON format.")
|
82
|
162
|
@pass_context
|
83
|
|
-def lists(context):
|
84
|
|
- context.logger.info("Getting list of operations")
|
|
163
|
+def lists(context, json):
|
85
|
164
|
stub = operations_pb2_grpc.OperationsStub(context.channel)
|
86
|
|
-
|
87
|
165
|
request = operations_pb2.ListOperationsRequest(name=context.instance_name)
|
88
|
166
|
|
89
|
167
|
response = stub.ListOperations(request)
|
90
|
168
|
|
91
|
169
|
if not response.operations:
|
92
|
|
- context.logger.warning("No operations to list")
|
|
170
|
+ click.echo('Error: No operations to list.', err=True)
|
93
|
171
|
return
|
94
|
172
|
|
95
|
|
- for op in response.operations:
|
96
|
|
- context.logger.info(op)
|
|
173
|
+ operations_map = OrderedDict([
|
|
174
|
+ ('CACHE_CHECK', []),
|
|
175
|
+ ('QUEUED', []),
|
|
176
|
+ ('EXECUTING', []),
|
|
177
|
+ ('COMPLETED', [])
|
|
178
|
+ ])
|
|
179
|
+
|
|
180
|
+ for operation in response.operations:
|
|
181
|
+ metadata = remote_execution_pb2.ExecuteOperationMetadata()
|
|
182
|
+ # The metadata is expected to be an ExecuteOperationMetadata message:
|
|
183
|
+ assert operation.metadata.Is(metadata.DESCRIPTOR)
|
|
184
|
+ operation.metadata.Unpack(metadata)
|
|
185
|
+
|
|
186
|
+ stage_name = remote_execution_pb2.ExecuteOperationMetadata.Stage.Name(
|
|
187
|
+ metadata.stage).upper()
|
|
188
|
+
|
|
189
|
+ operations_map[stage_name].append(operation)
|
|
190
|
+
|
|
191
|
+ for operations in operations_map.values():
|
|
192
|
+ operations.sort(key=attrgetter('name'))
|
|
193
|
+ for operation in operations:
|
|
194
|
+ if not json:
|
|
195
|
+ _print_operation_status(operation, metadata=metadata)
|
|
196
|
+ else:
|
|
197
|
+ click.echo(json_format.MessageToJson(operation))
|
97
|
198
|
|
98
|
199
|
|
99
|
200
|
@cli.command('wait', short_help="Streams an operation until it is complete.")
|
100
|
201
|
@click.argument('operation-name', nargs=1, type=click.STRING, required=True)
|
|
202
|
+@click.option('--json', is_flag=True, show_default=True,
|
|
203
|
+ help="Print operations statuses in JSON format.")
|
101
|
204
|
@pass_context
|
102
|
|
-def wait(context, operation_name):
|
|
205
|
+def wait(context, operation_name, json):
|
103
|
206
|
stub = remote_execution_pb2_grpc.ExecutionStub(context.channel)
|
104
|
207
|
request = remote_execution_pb2.WaitExecutionRequest(name=operation_name)
|
105
|
208
|
|
106
|
|
- response = stub.WaitExecution(request)
|
|
209
|
+ operation_iterator = stub.WaitExecution(request)
|
107
|
210
|
|
108
|
|
- for stream in response:
|
109
|
|
- context.logger.info(stream)
|
|
211
|
+ for operation in operation_iterator:
|
|
212
|
+ if not json and operation.done:
|
|
213
|
+ _print_operation_status(operation, print_details=True)
|
|
214
|
+ elif not json:
|
|
215
|
+ _print_operation_status(operation)
|
|
216
|
+ else:
|
|
217
|
+ click.echo(json_format.MessageToJson(operation))
|