Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Side by Side Diff: chrome/browser/chromeos/policy/upload_job_impl.cc

Issue 1875443003: Retry uploading in UploadJobImpl when error occurs (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Retry when token request failed, add backoff, add log messages Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/chromeos/policy/upload_job_impl.h" 5 #include "chrome/browser/chromeos/policy/upload_job_impl.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <set> 8 #include <set>
9 #include <utility> 9 #include <utility>
10 10
11 #include "base/location.h"
11 #include "base/logging.h" 12 #include "base/logging.h"
12 #include "base/macros.h" 13 #include "base/macros.h"
13 #include "base/strings/stringprintf.h" 14 #include "base/strings/stringprintf.h"
14 #include "google_apis/gaia/gaia_constants.h" 15 #include "google_apis/gaia/gaia_constants.h"
15 #include "google_apis/gaia/google_service_auth_error.h" 16 #include "google_apis/gaia/google_service_auth_error.h"
16 #include "net/base/mime_util.h" 17 #include "net/base/mime_util.h"
17 #include "net/http/http_status_code.h" 18 #include "net/http/http_status_code.h"
18 #include "net/url_request/url_request_status.h" 19 #include "net/url_request/url_request_status.h"
19 20
20 namespace policy { 21 namespace policy {
21 22
22 namespace { 23 namespace {
23 24
24 // Format for bearer tokens in HTTP requests to access OAuth 2.0 protected 25 // Format for bearer tokens in HTTP requests to access OAuth 2.0 protected
25 // resources. 26 // resources.
26 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s"; 27 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s";
27 28
28 // Value the "Content-Type" field will be set to in the POST request. 29 // Value the "Content-Type" field will be set to in the POST request.
29 const char kUploadContentType[] = "multipart/form-data"; 30 const char kUploadContentType[] = "multipart/form-data";
30 31
31 // Number of upload retries. 32 // Number of upload attempts.
32 const int kMaxRetries = 1; 33 const int kMaxAttempts = 4;
33 34
34 // Max size of MIME boundary according to RFC 1341, section 7.2.1. 35 // Max size of MIME boundary according to RFC 1341, section 7.2.1.
35 const size_t kMaxMimeBoundarySize = 70; 36 const size_t kMaxMimeBoundarySize = 70;
36 37
38 // Delay after first unsuccessful upload attempt. After each additional failure,
39 // the delay increases with this value.
Andrew T Wilson (Slow) 2016/05/02 09:15:49 Consider just having a simple fixed 25s delay - th
Marton Hunyady 2016/05/04 11:26:41 Done.
40 // Value chosen because the server is waiting 2 minutes for a screenshot.
41 // 1st attempt is 0s after request, 2nd: 18s, 3rd: 54s, 4th: 108s.
42 const long kRetryDelayMs = 18000;
43
37 } // namespace 44 } // namespace
38 45
39 UploadJobImpl::Delegate::~Delegate() { 46 UploadJobImpl::Delegate::~Delegate() {
40 } 47 }
41 48
42 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { 49 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() {
43 } 50 }
44 51
45 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { 52 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() {
46 } 53 }
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 const { 127 const {
121 return net::GenerateMimeMultipartBoundary(); 128 return net::GenerateMimeMultipartBoundary();
122 } 129 }
123 130
124 UploadJobImpl::UploadJobImpl( 131 UploadJobImpl::UploadJobImpl(
125 const GURL& upload_url, 132 const GURL& upload_url,
126 const std::string& account_id, 133 const std::string& account_id,
127 OAuth2TokenService* token_service, 134 OAuth2TokenService* token_service,
128 scoped_refptr<net::URLRequestContextGetter> url_context_getter, 135 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
129 Delegate* delegate, 136 Delegate* delegate,
130 std::unique_ptr<MimeBoundaryGenerator> boundary_generator) 137 std::unique_ptr<MimeBoundaryGenerator> boundary_generator,
138 const scoped_refptr<base::SequencedTaskRunner> task_runner)
Andrew T Wilson (Slow) 2016/05/02 09:15:49 pass as a reference
Marton Hunyady 2016/05/04 11:26:41 Removed const, passing as value.
131 : OAuth2TokenService::Consumer("cros_upload_job"), 139 : OAuth2TokenService::Consumer("cros_upload_job"),
132 upload_url_(upload_url), 140 upload_url_(upload_url),
133 account_id_(account_id), 141 account_id_(account_id),
134 token_service_(token_service), 142 token_service_(token_service),
135 url_context_getter_(url_context_getter), 143 url_context_getter_(url_context_getter),
136 delegate_(delegate), 144 delegate_(delegate),
137 boundary_generator_(std::move(boundary_generator)), 145 boundary_generator_(std::move(boundary_generator)),
138 state_(IDLE), 146 state_(IDLE),
139 retry_(0) { 147 retry_(0),
148 task_runner_(task_runner),
Andrew T Wilson (Slow) 2016/05/02 09:15:49 Now that we have a task_runner, we need to make su
Marton Hunyady 2016/05/04 11:26:41 Done.
149 weak_factory_(this) {
140 DCHECK(token_service_); 150 DCHECK(token_service_);
141 DCHECK(url_context_getter_); 151 DCHECK(url_context_getter_);
142 DCHECK(delegate_); 152 DCHECK(delegate_);
143 if (!upload_url_.is_valid()) { 153 if (!upload_url_.is_valid()) {
144 state_ = ERROR; 154 state_ = ERROR;
145 NOTREACHED() << upload_url_ << " is not a valid URL."; 155 NOTREACHED() << upload_url_ << " is not a valid URL.";
146 } 156 }
147 } 157 }
148 158
149 UploadJobImpl::~UploadJobImpl() { 159 UploadJobImpl::~UploadJobImpl() {
(...skipping 12 matching lines...) Expand all
162 std::unique_ptr<DataSegment> data_segment( 172 std::unique_ptr<DataSegment> data_segment(
163 new DataSegment(name, filename, std::move(data), header_entries)); 173 new DataSegment(name, filename, std::move(data), header_entries));
164 data_segments_.push_back(std::move(data_segment)); 174 data_segments_.push_back(std::move(data_segment));
165 } 175 }
166 176
167 void UploadJobImpl::Start() { 177 void UploadJobImpl::Start() {
168 // Cannot start an upload on a busy or failed instance. 178 // Cannot start an upload on a busy or failed instance.
169 DCHECK_EQ(IDLE, state_); 179 DCHECK_EQ(IDLE, state_);
170 if (state_ != IDLE) 180 if (state_ != IDLE)
171 return; 181 return;
182 retry_ = 0;
Andrew T Wilson (Slow) 2016/05/02 09:15:49 Why is this necessary? You can only get to IDLE st
Marton Hunyady 2016/05/04 11:26:41 Done.
172 RequestAccessToken(); 183 RequestAccessToken();
173 } 184 }
174 185
175 void UploadJobImpl::RequestAccessToken() { 186 void UploadJobImpl::RequestAccessToken() {
176 state_ = ACQUIRING_TOKEN; 187 state_ = ACQUIRING_TOKEN;
177 188
178 OAuth2TokenService::ScopeSet scope_set; 189 OAuth2TokenService::ScopeSet scope_set;
179 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); 190 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
180 access_token_request_ = 191 access_token_request_ =
181 token_service_->StartRequest(account_id_, scope_set, this); 192 token_service_->StartRequest(account_id_, scope_set, this);
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 268
258 upload_fetcher_ = 269 upload_fetcher_ =
259 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); 270 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this);
260 upload_fetcher_->SetRequestContext(url_context_getter_.get()); 271 upload_fetcher_->SetRequestContext(url_context_getter_.get());
261 upload_fetcher_->SetUploadData(content_type, *post_data_); 272 upload_fetcher_->SetUploadData(content_type, *post_data_);
262 upload_fetcher_->AddExtraRequestHeader( 273 upload_fetcher_->AddExtraRequestHeader(
263 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); 274 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
264 upload_fetcher_->Start(); 275 upload_fetcher_->Start();
265 } 276 }
266 277
267 void UploadJobImpl::StartUpload(const std::string& access_token) { 278 void UploadJobImpl::StartUpload() {
268 if (!SetUpMultipart()) { 279 if (!SetUpMultipart()) {
269 LOG(ERROR) << "Multipart message assembly failed."; 280 LOG(ERROR) << "Multipart message assembly failed.";
270 state_ = ERROR; 281 state_ = ERROR;
271 return; 282 return;
272 } 283 }
273 CreateAndStartURLFetcher(access_token); 284 CreateAndStartURLFetcher(access_token_);
274 state_ = UPLOADING; 285 state_ = UPLOADING;
275 } 286 }
276 287
277 void UploadJobImpl::OnGetTokenSuccess( 288 void UploadJobImpl::OnGetTokenSuccess(
278 const OAuth2TokenService::Request* request, 289 const OAuth2TokenService::Request* request,
279 const std::string& access_token, 290 const std::string& access_token,
280 const base::Time& expiration_time) { 291 const base::Time& expiration_time) {
281 DCHECK_EQ(ACQUIRING_TOKEN, state_); 292 DCHECK_EQ(ACQUIRING_TOKEN, state_);
282 DCHECK_EQ(access_token_request_.get(), request); 293 DCHECK_EQ(access_token_request_.get(), request);
283 access_token_request_.reset(); 294 access_token_request_.reset();
284 295
285 // Also cache the token locally, so that we can revoke it later if necessary. 296 // Also cache the token locally, so that we can revoke it later if necessary.
286 access_token_ = access_token; 297 access_token_ = access_token;
287 StartUpload(access_token); 298 StartUpload();
288 } 299 }
289 300
290 void UploadJobImpl::OnGetTokenFailure( 301 void UploadJobImpl::OnGetTokenFailure(
291 const OAuth2TokenService::Request* request, 302 const OAuth2TokenService::Request* request,
292 const GoogleServiceAuthError& error) { 303 const GoogleServiceAuthError& error) {
293 DCHECK_EQ(ACQUIRING_TOKEN, state_); 304 DCHECK_EQ(ACQUIRING_TOKEN, state_);
294 DCHECK_EQ(access_token_request_.get(), request); 305 DCHECK_EQ(access_token_request_.get(), request);
295 access_token_request_.reset();
Andrew T Wilson (Slow) 2016/05/02 09:15:49 Why did you remove this? I think you still want to
Marton Hunyady 2016/05/04 11:26:41 Done.
296 LOG(ERROR) << "Token request failed: " << error.ToString(); 306 LOG(ERROR) << "Token request failed: " << error.ToString();
297 state_ = ERROR; 307 HandleError(AUTHENTICATION_ERROR);
298 delegate_->OnFailure(AUTHENTICATION_ERROR); 308 }
309
310 void UploadJobImpl::HandleError(ErrorCode errorCode) {
311 retry_++;
Andrew T Wilson (Slow) 2016/05/02 09:15:49 Should log the errorCode here.
Marton Hunyady 2016/05/04 11:26:41 Done.
312 upload_fetcher_.reset();
313 if (retry_ >= kMaxAttempts) {
314 // Maximum number of attempts reached, failure.
315 LOG(ERROR) << "Upload failed, maximum number of attempts reached.";
316 access_token_.clear();
317 post_data_.reset();
318 state_ = ERROR;
319 delegate_->OnFailure(errorCode);
320 } else {
321 if (errorCode == AUTHENTICATION_ERROR) {
322 LOG(ERROR) << "Upload failed, retrying upload with a new token.";
323 // Request new token and retry.
324 OAuth2TokenService::ScopeSet scope_set;
325 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
326 token_service_->InvalidateAccessToken(account_id_, scope_set,
327 access_token_);
328 access_token_.clear();
329 task_runner_->PostDelayedTask(
330 FROM_HERE, base::Bind(&UploadJobImpl::RequestAccessToken,
331 weak_factory_.GetWeakPtr()),
332 base::TimeDelta::FromMilliseconds(kRetryDelayMs * retry_));
333 } else {
334 // Retry without a new token.
335 state_ = ACQUIRING_TOKEN;
336 LOG(WARNING) << "Upload failed, retrying upload with the same token.";
337 task_runner_->PostDelayedTask(
338 FROM_HERE,
339 base::Bind(&UploadJobImpl::StartUpload, weak_factory_.GetWeakPtr()),
340 base::TimeDelta::FromMilliseconds(kRetryDelayMs * retry_));
341 }
342 }
299 } 343 }
300 344
301 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { 345 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) {
302 DCHECK_EQ(upload_fetcher_.get(), source); 346 DCHECK_EQ(upload_fetcher_.get(), source);
347 DCHECK_EQ(UPLOADING, state_);
303 const net::URLRequestStatus& status = source->GetStatus(); 348 const net::URLRequestStatus& status = source->GetStatus();
304 if (!status.is_success()) { 349 if (!status.is_success()) {
305 LOG(ERROR) << "URLRequestStatus error " << status.error(); 350 LOG(ERROR) << "URLRequestStatus error " << status.error();
306 upload_fetcher_.reset(); 351 HandleError(NETWORK_ERROR);
307 state_ = ERROR; 352 } else {
308 post_data_.reset(); 353 const int response_code = source->GetResponseCode();
309 delegate_->OnFailure(NETWORK_ERROR); 354 if (response_code == net::HTTP_OK) {
310 return; 355 // Successful uploading
Andrew T Wilson (Slow) 2016/05/02 09:15:49 nit: uploading -> upload.
Marton Hunyady 2016/05/04 11:26:41 Done.
311 }
312
313 const int response_code = source->GetResponseCode();
314 const bool success = response_code == net::HTTP_OK;
315 if (!success)
316 LOG(ERROR) << "POST request failed with HTTP status code " << response_code;
317
318 if (response_code == net::HTTP_UNAUTHORIZED) {
319 if (retry_ >= kMaxRetries) {
320 upload_fetcher_.reset(); 356 upload_fetcher_.reset();
357 access_token_.clear();
358 post_data_.reset();
359 state_ = SUCCESS;
360 delegate_->OnSuccess();
361 } else if (response_code == net::HTTP_UNAUTHORIZED) {
321 LOG(ERROR) << "Unauthorized request."; 362 LOG(ERROR) << "Unauthorized request.";
322 state_ = ERROR; 363 HandleError(AUTHENTICATION_ERROR);
323 post_data_.reset(); 364 } else {
324 delegate_->OnFailure(AUTHENTICATION_ERROR); 365 LOG(ERROR) << "POST request failed with HTTP status code "
325 return; 366 << response_code << ".";
367 HandleError(SERVER_ERROR);
326 } 368 }
327 retry_++;
328 upload_fetcher_.reset();
329 OAuth2TokenService::ScopeSet scope_set;
330 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
331 token_service_->InvalidateAccessToken(account_id_, scope_set,
332 access_token_);
333 access_token_.clear();
334 RequestAccessToken();
335 return;
336 }
337
338 upload_fetcher_.reset();
339 access_token_.clear();
340 upload_fetcher_.reset();
341 post_data_.reset();
342 if (success) {
343 state_ = SUCCESS;
344 delegate_->OnSuccess();
345 } else {
346 state_ = ERROR;
347 delegate_->OnFailure(SERVER_ERROR);
348 } 369 }
349 } 370 }
350 371
351 } // namespace policy 372 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698