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

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: Threading checks, logging, test improvements 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 each unsuccessful upload attempt.
39 const long kRetryDelayMs = 25000;
40
37 } // namespace 41 } // namespace
38 42
39 UploadJobImpl::Delegate::~Delegate() { 43 UploadJobImpl::Delegate::~Delegate() {
40 } 44 }
41 45
42 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { 46 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() {
43 } 47 }
44 48
45 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { 49 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() {
46 } 50 }
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 const { 124 const {
121 return net::GenerateMimeMultipartBoundary(); 125 return net::GenerateMimeMultipartBoundary();
122 } 126 }
123 127
124 UploadJobImpl::UploadJobImpl( 128 UploadJobImpl::UploadJobImpl(
125 const GURL& upload_url, 129 const GURL& upload_url,
126 const std::string& account_id, 130 const std::string& account_id,
127 OAuth2TokenService* token_service, 131 OAuth2TokenService* token_service,
128 scoped_refptr<net::URLRequestContextGetter> url_context_getter, 132 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
129 Delegate* delegate, 133 Delegate* delegate,
130 std::unique_ptr<MimeBoundaryGenerator> boundary_generator) 134 std::unique_ptr<MimeBoundaryGenerator> boundary_generator,
135 scoped_refptr<base::SequencedTaskRunner> task_runner)
131 : OAuth2TokenService::Consumer("cros_upload_job"), 136 : OAuth2TokenService::Consumer("cros_upload_job"),
132 upload_url_(upload_url), 137 upload_url_(upload_url),
133 account_id_(account_id), 138 account_id_(account_id),
134 token_service_(token_service), 139 token_service_(token_service),
135 url_context_getter_(url_context_getter), 140 url_context_getter_(url_context_getter),
136 delegate_(delegate), 141 delegate_(delegate),
137 boundary_generator_(std::move(boundary_generator)), 142 boundary_generator_(std::move(boundary_generator)),
138 state_(IDLE), 143 state_(IDLE),
139 retry_(0) { 144 retry_(0),
145 task_runner_(task_runner),
146 weak_factory_(this) {
140 DCHECK(token_service_); 147 DCHECK(token_service_);
141 DCHECK(url_context_getter_); 148 DCHECK(url_context_getter_);
142 DCHECK(delegate_); 149 DCHECK(delegate_);
143 if (!upload_url_.is_valid()) { 150 if (!upload_url_.is_valid()) {
144 state_ = ERROR; 151 state_ = ERROR;
145 NOTREACHED() << upload_url_ << " is not a valid URL."; 152 NOTREACHED() << upload_url_ << " is not a valid URL.";
146 } 153 }
147 } 154 }
148 155
149 UploadJobImpl::~UploadJobImpl() { 156 UploadJobImpl::~UploadJobImpl() {
150 } 157 }
151 158
152 void UploadJobImpl::AddDataSegment( 159 void UploadJobImpl::AddDataSegment(
153 const std::string& name, 160 const std::string& name,
154 const std::string& filename, 161 const std::string& filename,
155 const std::map<std::string, std::string>& header_entries, 162 const std::map<std::string, std::string>& header_entries,
156 std::unique_ptr<std::string> data) { 163 std::unique_ptr<std::string> data) {
157 // Cannot add data to busy or failed instance. 164 // Cannot add data to busy or failed instance.
158 DCHECK_EQ(IDLE, state_); 165 DCHECK_EQ(IDLE, state_);
159 if (state_ != IDLE) 166 if (state_ != IDLE)
160 return; 167 return;
168 DCHECK(thread_checker_.CalledOnValidThread());
161 169
162 std::unique_ptr<DataSegment> data_segment( 170 std::unique_ptr<DataSegment> data_segment(
163 new DataSegment(name, filename, std::move(data), header_entries)); 171 new DataSegment(name, filename, std::move(data), header_entries));
164 data_segments_.push_back(std::move(data_segment)); 172 data_segments_.push_back(std::move(data_segment));
165 } 173 }
166 174
167 void UploadJobImpl::Start() { 175 void UploadJobImpl::Start() {
168 // Cannot start an upload on a busy or failed instance. 176 // Cannot start an upload on a busy or failed instance.
169 DCHECK_EQ(IDLE, state_); 177 DCHECK_EQ(IDLE, state_);
170 if (state_ != IDLE) 178 if (state_ != IDLE)
171 return; 179 return;
180 DCHECK_EQ(0, retry_);
181 DCHECK(thread_checker_.CalledOnValidThread());
Andrew T Wilson (Slow) 2016/05/10 22:00:42 Move call to CalledOnValidThread() to the top of t
Marton Hunyady 2016/05/11 12:55:30 Done.
182
172 RequestAccessToken(); 183 RequestAccessToken();
173 } 184 }
174 185
175 void UploadJobImpl::RequestAccessToken() { 186 void UploadJobImpl::RequestAccessToken() {
187 DCHECK(!access_token_request_);
188 DCHECK(thread_checker_.CalledOnValidThread());
189
176 state_ = ACQUIRING_TOKEN; 190 state_ = ACQUIRING_TOKEN;
177 191
178 OAuth2TokenService::ScopeSet scope_set; 192 OAuth2TokenService::ScopeSet scope_set;
179 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); 193 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
180 access_token_request_ = 194 access_token_request_ =
181 token_service_->StartRequest(account_id_, scope_set, this); 195 token_service_->StartRequest(account_id_, scope_set, this);
182 } 196 }
183 197
184 bool UploadJobImpl::SetUpMultipart() { 198 bool UploadJobImpl::SetUpMultipart() {
185 DCHECK_EQ(ACQUIRING_TOKEN, state_); 199 DCHECK_EQ(ACQUIRING_TOKEN, state_);
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 271
258 upload_fetcher_ = 272 upload_fetcher_ =
259 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); 273 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this);
260 upload_fetcher_->SetRequestContext(url_context_getter_.get()); 274 upload_fetcher_->SetRequestContext(url_context_getter_.get());
261 upload_fetcher_->SetUploadData(content_type, *post_data_); 275 upload_fetcher_->SetUploadData(content_type, *post_data_);
262 upload_fetcher_->AddExtraRequestHeader( 276 upload_fetcher_->AddExtraRequestHeader(
263 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); 277 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
264 upload_fetcher_->Start(); 278 upload_fetcher_->Start();
265 } 279 }
266 280
267 void UploadJobImpl::StartUpload(const std::string& access_token) { 281 void UploadJobImpl::StartUpload() {
282 DCHECK(thread_checker_.CalledOnValidThread());
283
268 if (!SetUpMultipart()) { 284 if (!SetUpMultipart()) {
269 LOG(ERROR) << "Multipart message assembly failed."; 285 LOG(ERROR) << "Multipart message assembly failed.";
270 state_ = ERROR; 286 state_ = ERROR;
271 return; 287 return;
272 } 288 }
273 CreateAndStartURLFetcher(access_token); 289 CreateAndStartURLFetcher(access_token_);
274 state_ = UPLOADING; 290 state_ = UPLOADING;
275 } 291 }
276 292
277 void UploadJobImpl::OnGetTokenSuccess( 293 void UploadJobImpl::OnGetTokenSuccess(
278 const OAuth2TokenService::Request* request, 294 const OAuth2TokenService::Request* request,
279 const std::string& access_token, 295 const std::string& access_token,
280 const base::Time& expiration_time) { 296 const base::Time& expiration_time) {
281 DCHECK_EQ(ACQUIRING_TOKEN, state_); 297 DCHECK_EQ(ACQUIRING_TOKEN, state_);
282 DCHECK_EQ(access_token_request_.get(), request); 298 DCHECK_EQ(access_token_request_.get(), request);
283 access_token_request_.reset(); 299 access_token_request_.reset();
284 300
285 // Also cache the token locally, so that we can revoke it later if necessary. 301 // Also cache the token locally, so that we can revoke it later if necessary.
286 access_token_ = access_token; 302 access_token_ = access_token;
287 StartUpload(access_token); 303 StartUpload();
288 } 304 }
289 305
290 void UploadJobImpl::OnGetTokenFailure( 306 void UploadJobImpl::OnGetTokenFailure(
291 const OAuth2TokenService::Request* request, 307 const OAuth2TokenService::Request* request,
292 const GoogleServiceAuthError& error) { 308 const GoogleServiceAuthError& error) {
293 DCHECK_EQ(ACQUIRING_TOKEN, state_); 309 DCHECK_EQ(ACQUIRING_TOKEN, state_);
294 DCHECK_EQ(access_token_request_.get(), request); 310 DCHECK_EQ(access_token_request_.get(), request);
295 access_token_request_.reset(); 311 access_token_request_.reset();
296 LOG(ERROR) << "Token request failed: " << error.ToString(); 312 LOG(ERROR) << "Token request failed: " << error.ToString();
297 state_ = ERROR; 313 HandleError(AUTHENTICATION_ERROR);
298 delegate_->OnFailure(AUTHENTICATION_ERROR); 314 }
315
316 void UploadJobImpl::HandleError(ErrorCode errorCode) {
Andrew T Wilson (Slow) 2016/05/10 22:00:42 errorCode -> error_code
Marton Hunyady 2016/05/11 12:55:30 Done.
317 retry_++;
318 upload_fetcher_.reset();
319
320 std::string errorString;
Andrew T Wilson (Slow) 2016/05/10 22:00:42 errorString->error_string
Marton Hunyady 2016/05/11 12:55:30 Done.
321 switch (errorCode) {
322 case NETWORK_ERROR:
323 errorString = "NETWORK_ERROR";
324 break;
325 case AUTHENTICATION_ERROR:
326 errorString = "AUTHENTICATION_ERROR";
327 break;
328 case SERVER_ERROR:
329 errorString = "SERVER_ERROR";
330 break;
331 }
332 LOG(ERROR) << "Upload failed: " << errorString;
Andrew T Wilson (Slow) 2016/05/10 22:00:42 This is OK, but I think it's superior to just log
Marton Hunyady 2016/05/11 12:55:30 Done.
333
334 if (retry_ >= kMaxAttempts) {
335 // Maximum number of attempts reached, failure.
336 LOG(ERROR) << "Maximum number of attempts reached.";
337 access_token_.clear();
338 post_data_.reset();
339 state_ = ERROR;
340 delegate_->OnFailure(errorCode);
341 } else {
342 if (errorCode == AUTHENTICATION_ERROR) {
343 LOG(ERROR) << "Retrying upload with a new token.";
344 // Request new token and retry.
345 OAuth2TokenService::ScopeSet scope_set;
346 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
347 token_service_->InvalidateAccessToken(account_id_, scope_set,
348 access_token_);
349 access_token_.clear();
350 task_runner_->PostDelayedTask(
351 FROM_HERE, base::Bind(&UploadJobImpl::RequestAccessToken,
352 weak_factory_.GetWeakPtr()),
353 base::TimeDelta::FromMilliseconds(kRetryDelayMs));
354 } else {
355 // Retry without a new token.
356 state_ = ACQUIRING_TOKEN;
357 LOG(WARNING) << "Retrying upload with the same token.";
358 task_runner_->PostDelayedTask(
359 FROM_HERE,
360 base::Bind(&UploadJobImpl::StartUpload, weak_factory_.GetWeakPtr()),
361 base::TimeDelta::FromMilliseconds(kRetryDelayMs));
362 }
363 }
299 } 364 }
300 365
301 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { 366 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) {
302 DCHECK_EQ(upload_fetcher_.get(), source); 367 DCHECK_EQ(upload_fetcher_.get(), source);
368 DCHECK_EQ(UPLOADING, state_);
303 const net::URLRequestStatus& status = source->GetStatus(); 369 const net::URLRequestStatus& status = source->GetStatus();
304 if (!status.is_success()) { 370 if (!status.is_success()) {
305 LOG(ERROR) << "URLRequestStatus error " << status.error(); 371 LOG(ERROR) << "URLRequestStatus error " << status.error();
306 upload_fetcher_.reset(); 372 HandleError(NETWORK_ERROR);
307 state_ = ERROR; 373 } else {
308 post_data_.reset(); 374 const int response_code = source->GetResponseCode();
309 delegate_->OnFailure(NETWORK_ERROR); 375 if (response_code == net::HTTP_OK) {
310 return; 376 // Successful upload
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(); 377 upload_fetcher_.reset();
378 access_token_.clear();
379 post_data_.reset();
380 state_ = SUCCESS;
381 delegate_->OnSuccess();
382 } else if (response_code == net::HTTP_UNAUTHORIZED) {
321 LOG(ERROR) << "Unauthorized request."; 383 LOG(ERROR) << "Unauthorized request.";
322 state_ = ERROR; 384 HandleError(AUTHENTICATION_ERROR);
323 post_data_.reset(); 385 } else {
324 delegate_->OnFailure(AUTHENTICATION_ERROR); 386 LOG(ERROR) << "POST request failed with HTTP status code "
325 return; 387 << response_code << ".";
388 HandleError(SERVER_ERROR);
326 } 389 }
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 } 390 }
349 } 391 }
350 392
351 } // namespace policy 393 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698