libzypp 17.36.6
commitpackagepreloader.cc
Go to the documentation of this file.
5#include <zypp/media/MediaCurl2.h> // for shared logic like authenticate
6#include <zypp/media/MediaHandlerFactory.h> // to detect the URL type
14#include <zypp/MediaSetAccess.h>
15#include <zypp/Package.h>
16#include <zypp/SrcPackage.h>
17#include <zypp/ZConfig.h>
18
19namespace zypp {
20
21 namespace {
22
23 inline bool preloadEnabled()
24 {
25 const char *val = ::getenv("ZYPP_PCK_PRELOAD");
26
27 // opt out for now
28 if ( !val )
29 return false;
30
31 return ( std::string_view( val ) == "1" );
32 }
33
34 zypp::Pathname pckCachedLocation ( const PoolItem &pck ) {
35 if ( pck.isKind<Package>() ) {
36 return pck->asKind<Package>()->cachedLocation();
37 } else if ( pck.isKind<SrcPackage>() ) {
38 return pck->asKind<SrcPackage>()->cachedLocation();
39 }
40 return {};
41 }
42
43 }
44
49
51 public:
52 enum State {
55 //ZckHead,
56 //ZckData,
58 };
59
61
62 bool finished ( ) const {
63 return (_s == Finished);
64 }
65
66 void nextJob () {
67
68 // clean state vars
69 _started = false;
70 _firstAuth = true;
72 _tmpFile.reset();
74 _taintedMirrors.clear();
75
76 if ( _parent._requiredDls.empty() ) {
77
78 if ( _myMirror ) {
79 _myMirror->refs--;
80 _myMirror = nullptr;
82 }
83
84 MIL << "No more jobs pending, exiting worker" << std::endl;
85 // exit!
86 _s = Finished;
87 _sigFinished.emit();
88 return;
89 }
90
91 _job = _parent._requiredDls.front();
92 _parent._requiredDls.pop_front();
93
94 auto loc = _job.lookupLocation();
95 _targetPath = _job.repoInfo().predownloadPath() / _job.lookupLocation().filename();
96
97 // select a mirror we want to use
98 if ( !prepareMirror( ) ) {
99 callback::UserData userData( "CommitPreloadReport/fileDone" );
100 userData.set( "description", _("No mirror found") );
101 _parent._report->fileDone( _targetPath, media::CommitPreloadReport::ERROR, userData );
102 return nextJob();
103 }
104
105 if ( filesystem::assert_dir( _targetPath.dirname()) != 0 ) {
106 ERR << "Failed to create target dir for file: " << _targetPath << std::endl;
107 callback::UserData userData( "CommitPreloadReport/fileDone" );
108 userData.set( "description", _("Could not create target File") );
109 _parent._report->fileDone( _targetPath, media::CommitPreloadReport::ERROR, userData );
110 return nextJob();
111 }
112
113
116 makeJobUrl ( url, settings );
117
118 // check if the file is there already
119 {
120 PathInfo pathInfo(_targetPath);
121 if ( pathInfo.isExist() ) {
122 // just in case there is something else that is not a file we delete it
123 if ( !pathInfo.isFile() ) {
124 if ( pathInfo.isDir () )
126 else
128
129 } else if ( is_checksum( _targetPath, loc.checksum() ) ) {
130
131 // if we have the file already, no need to download again
132 callback::UserData userData( "CommitPreloadReport/fileDone" );
133 userData.set( "Url", url );
134 userData.set( "description", _("Already in Cache") );
135 _parent._report->fileDone( _targetPath, media::CommitPreloadReport::NO_ERROR, userData );
136
137 return nextJob();
138
139 } else {
140 // everything else we delete
142 }
143 }
144 }
145
146 // we download into a temp file so that we don't leave broken files in case of errors or a crash
148
149 if ( _s == Pending ) {
150 // init case, set up request
151 _req = std::make_shared<zyppng::NetworkRequest>( url, _tmpFile );
155 } else {
156 _req->resetRequestRanges();
157 _req->setUrl( url );
158 _req->setTargetFilePath( _tmpFile );
159 }
160
161 // TODO check for zchunk
162
163 _s = SimpleDl;
164 _req->transferSettings() = settings;
165 _parent._dispatcher->enqueue(_req);
166 }
167
171
172 private:
173
174 // TODO some smarter logic that selects mirrors
176
177 const auto &pi = _job;
178
179 if ( _myMirror ) {
180 if ( _currentRepoId == pi.repository().id() ) {
181 return true;
182 }
184 _myMirror->refs--;
185 _myMirror = nullptr;
186 }
187
189 if ( !_myMirror )
190 return false;
191
192 _currentRepoId = pi.repository().id();
193 _myMirror->refs++;
194 return true;
195 }
196
201
202 if ( _myMirror ) {
203 _myMirror->miss++;
204 _taintedMirrors.insert( _myMirror );
205 }
206
207 // try to find another mirror
208 auto mirrPtr = findUsableMirror ( _myMirror, false );
209 if ( mirrPtr ) {
210 if ( _myMirror ) {
211 _myMirror->refs--;
212 }
213 _myMirror = mirrPtr;
214 _myMirror->refs++;
215 return true;
216 }
217 return false;
218 }
219
223 RepoUrl *findUsableMirror( RepoUrl *skip = nullptr, bool allowTainted = true ) {
224 auto &repoDlInfo = _parent._dlRepoInfo.at( _job.repository().id() );
225
226 std::vector<RepoUrl>::iterator curr = repoDlInfo._baseUrls.end();
227 int currentSmallestRef = INT_MAX;
228
229 for ( auto i = repoDlInfo._baseUrls.begin(); i != repoDlInfo._baseUrls.end(); i++ ) {
230 auto mirrorPtr = &(*i);
231
232 if ( skip == mirrorPtr )
233 continue;
234
235 if ( !allowTainted && _taintedMirrors.find(mirrorPtr) != _taintedMirrors.end() )
236 continue;
237
238 // we are adding the file misses on top of the refcount
239 // that way we will use mirrors that often miss a file less
240 if ( ( i->refs + i->miss ) < currentSmallestRef ) {
241 currentSmallestRef = ( i->refs + i->miss );
242 curr = i;
243 }
244 }
245
246 if ( curr == repoDlInfo._baseUrls.end() )
247 return nullptr;
248 return &(*curr);
249 }
250
252 MIL << "Request for " << req.url() << " started" << std::endl;
253 }
254
256 if ( !_started ) {
257 _started = true;
258
259 callback::UserData userData( "CommitPreloadReport/fileStart" );
260 userData.set( "Url", _req->url() );
261 _parent._report->fileStart( _targetPath, userData );
262 }
263
264 ByteCount downloaded;
265 if ( _lastByteCount == 0 )
266 downloaded = count;
267 else
268 downloaded = count - _lastByteCount;
269 _lastByteCount = count;
270
271 _parent.reportBytesDownloaded( downloaded );
272 }
273
275 MIL << "Request for " << req.url() << " finished. (" << err.toString() << ")" << std::endl;
276 if ( !req.hasError() ) {
277 if ( filesystem::rename( _tmpFile, _targetPath ) != 0 ) {
278 // error
279 failCurrentJob ( _targetPath, req.url(), media::CommitPreloadReport::ERROR, _("Failed to rename temporary file.") );
280 } else {
281 callback::UserData userData( "CommitPreloadReport/fileDone" );
282 userData.set( "Url", req.url() );
283 userData.set( "description", _("Finished") );
284 _parent._report->fileDone( _targetPath, media::CommitPreloadReport::NO_ERROR, userData );
285 }
286 } else {
287 // handle errors and auth
288 const auto &error = req.error();
289 switch ( error.type() ) {
307 break;
308 }
311
312 //in case we got a auth hint from the server the error object will contain it
313 std::string authHint = error.extraInfoValue("authHint", std::string());
314
316 bool newCreds = media::MediaCurl2::authenticate( _myMirror->baseUrl, cm, req.transferSettings(), authHint, _firstAuth );
317 if ( newCreds) {
318 _firstAuth = false;
319 _parent._dispatcher->enqueue( _req );
320 return;
321 }
322
324 break;
325 }
327
328 MIL << "Download from mirror failed for file " << req.url () << " trying to taint mirror and move on" << std::endl;
329
330 if ( taintCurrentMirror() ) {
332
335 makeJobUrl ( url, settings );
336
337 MIL << "Found new mirror: " << url << " recovering, retry count: " << _notFoundRetry << std::endl;
338
339 _req->setUrl( url );
340 _req->transferSettings () = settings;
341
342 _parent._dispatcher->enqueue( _req );
343 return;
344 }
345
347 break;
348 }
350 // should never happen
351 DBG << "BUG: Download error flag is set , but Error code is NoError" << std::endl;
352 break;
353 }
354 }
355 nextJob();
356 }
357
358 void failCurrentJob( const zypp::Pathname &localPath, const std::optional<zypp::Url> &url, media::CommitPreloadReport::Error e, const std::optional<std::string> &errorMessage ) {
359
360 callback::UserData userData( "CommitPreloadReport/fileDone" );
361 if ( url )
362 userData.set( "Url", url );
363 if ( errorMessage )
364 userData.set( "description", _("Already in Cache") );
365
366 _parent._missedDownloads = true;
367 _parent._report->fileDone( localPath, e, userData );
368 }
369
370 void makeJobUrl ( zypp::Url &resultUrl, media::TransferSettings &resultSet ) {
371
372 // rewrite Url
373 zypp::Url url = _myMirror->baseUrl;
375
376 // TODO share logic with MediaCurl2
378
379 // if the proxy was not set (or explicitly unset) by url, then look...
380 if ( settings.proxy().empty() )
382
383 // remove extra options from the URL
385
386 const auto &loc = _job.lookupLocation();
387
388 // rewrite URL for media handle
389 url = MediaSetAccess::rewriteUrl( url ,loc.medianr() );
390
391 // append path to file
392 url.appendPathName( loc.filename() );
393
394 // add extra headers
395 for ( const auto & el : _myMirror->headers ) {
396 std::string header { el.first };
397 header += ": ";
398 header += el.second;
399 MIL << "Added custom header -> " << header << std::endl;
400 settings.addHeader( std::move(header) );
401 }
402
403 resultUrl = url;
404 resultSet = settings;
405 }
406
407 private:
410 zyppng::NetworkRequestRef _req;
411
415 bool _started = false;
416 bool _firstAuth = true;
417 RepoUrl *_myMirror = nullptr;
420
421 // retry handling
423 std::set<RepoUrl *> _taintedMirrors; //< mirrors that returned 404 for the current request
424
426
427 };
428
431
432 void CommitPackagePreloader::preloadTransaction( const std::vector<sat::Transaction::Step> &steps)
433 {
434 if ( !preloadEnabled() ) {
435 MIL << "CommitPackagePreloader disabled" << std::endl;
436 return;
437 }
438
439 // preload happens only if someone handles the report
440 if ( !_report->connected() ) {
441 MIL << "No receiver for the CommitPreloadReport, skipping preload phase" << std::endl;
442 return;
443 }
444
445 auto ev = zyppng::EventLoop::create();
446 _dispatcher = std::make_shared<zyppng::NetworkRequestDispatcher>();
447 _dispatcher->setMaximumConcurrentConnections( MediaConfig::instance().download_max_concurrent_connections() );
449 _dispatcher->setHostSpecificHeader ("download.opensuse.org", "X-ZYpp-DistributionFlavor", str::asString(media::MediaCurl2::distributionFlavorHeader()) );
450 _dispatcher->setHostSpecificHeader ("download.opensuse.org", "X-ZYpp-AnonymousId", str::asString(media::MediaCurl2::anonymousIdHeader()) );
451 _dispatcher->run();
452
453 _pTracker = std::make_shared<internal::ProgressTracker>();
454 _requiredBytes = 0;
456 _missedDownloads = false;
457
458 zypp_defer {
459 _dispatcher.reset();
460 _pTracker.reset();
461 };
462
463 for ( const auto &step : steps ) {
464 switch ( step.stepType() )
465 {
468 // proceed: only install actions may require download.
469 break;
470
471 default:
472 // next: no download for non-packages and delete actions.
473 continue;
474 break;
475 }
476
477 PoolItem pi(step.satSolvable());
478
479 if ( !pi->isKind<Package>() && !pi->isKind<SrcPackage>() )
480 continue;
481
482 // no checksum ,no predownload, Fetcher would ignore it
483 if ( pi->lookupLocation().checksum().empty() )
484 continue;
485
486 // check if Package is cached already
487 if( !pckCachedLocation(pi).empty() )
488 continue;
489
490 auto repoDlsIter = _dlRepoInfo.find( pi.repository().id() );
491 if ( repoDlsIter == _dlRepoInfo.end() ) {
492
493 // make sure download path for this repo exists
494 if ( filesystem::assert_dir( pi.repoInfo().predownloadPath() ) != 0 ) {
495 ERR << "Failed to create predownload cache for repo " << pi.repoInfo().alias() << std::endl;
496 return;
497 }
498
499 // filter base URLs that do not download
500 std::vector<RepoUrl> repoUrls;
501 const auto bu = pi.repoInfo().baseUrls();
502 std::for_each( bu.begin(), bu.end(), [&]( const zypp::Url &u ) {
503 media::UrlResolverPlugin::HeaderList custom_headers;
504 Url url = media::UrlResolverPlugin::resolveUrl(u, custom_headers);
505 MIL << "Trying scheme '" << url.getScheme() << "'" << std::endl;
506
507 if ( media::MediaHandlerFactory::handlerType(url) != media::MediaHandlerFactory::MediaCURLType )
508 return;
509
510 repoUrls.push_back( RepoUrl {
511 .baseUrl = std::move(url),
512 .headers = std::move(custom_headers)
513 } );
514 });
515
516 // skip this solvable if it has no downloading base URLs
517 if( repoUrls.empty() ) {
518 MIL << "Skipping predownload for " << step.satSolvable() << " no downloading URL" << std::endl;
519 continue;
520 }
521
522 // TODO here we could block to fetch mirror informations, either if the RepoInfo has a metalink or mirrorlist entry
523 // or if the hostname of the repo is d.o.o
524 if ( repoUrls.begin()->baseUrl.getHost() == "download.opensuse.org" ){
525 //auto req = std::make_shared<zyppng::NetworkRequest>( );
526 }
527
528 _dlRepoInfo.insert( std::make_pair(
529 pi.repository().id(),
531 ._baseUrls = std::move(repoUrls)
532 }
533 ));
534 }
535
536
537 _requiredBytes += pi.lookupLocation().downloadSize();
538 _requiredDls.push_back( pi );
539 }
540
541 if ( _requiredDls.empty() )
542 return;
543
544 // order by repo
545 std::sort( _requiredDls.begin(), _requiredDls.end(), []( const PoolItem &a , const PoolItem &b ) { return a.repository() < b.repository(); });
546
547 const auto &workerDone = [&, this](){
548 if ( std::all_of( _workers.begin(), _workers.end(), []( const auto &w ) { return w->finished();} ) )
549 ev->quit();
550 };
551
552 _report->start();
553 zypp_defer {
554 _report->finish( _missedDownloads ? media::CommitPreloadReport::MISS : media::CommitPreloadReport::SUCCESS );
555 };
556
557 MIL << "Downloading packages via " << MediaConfig::instance().download_max_concurrent_connections() << " connections." << std::endl;
558
559 // we start a worker for each configured connection
560 for ( int i = 0; i < MediaConfig::instance().download_max_concurrent_connections() ; i++ ) {
561 // if we run out of jobs before we started all workers, stop
562 if (_requiredDls.empty())
563 break;
564 auto worker = std::make_shared<PreloadWorker>(*this);
565 worker->sigWorkerFinished().connect(workerDone);
566 worker->nextJob();
567 _workers.push_back( std::move(worker) );
568 }
569
570 if( std::any_of( _workers.begin(), _workers.end(), []( const auto &w ) { return !w->finished(); } ) ) {
571 MIL << "Running preload event loop!" << std::endl;
572 ev->run();
573 }
574
575 MIL << "Preloading done, returning" << std::endl;
576 }
577
579 {
580 if ( !preloadEnabled() ) {
581 MIL << "CommitPackagePreloader disabled" << std::endl;
582 return;
583 }
584 std::for_each( _dlRepoInfo.begin (), _dlRepoInfo.end(), []( const auto &elem ){
585 filesystem::clean_dir ( Repository(elem.first).info().predownloadPath() );
586 });
587 }
588
590 {
591 return _missedDownloads;
592 }
593
595 {
596 _downloadedBytes += newBytes;
598
599 callback::UserData userData( "CommitPreloadReport/progress" );
600 userData.set( "dbps_avg" , static_cast<double>( _pTracker->_drateTotal ) );
601 userData.set( "dbps_current", static_cast<double>( _pTracker->_drateLast ) );
602 userData.set( "bytesReceived", static_cast<double>( _pTracker->_dnlNow ) );
603 userData.set( "bytesRequired", static_cast<double>( _pTracker->_dnlTotal ) );
604 if ( !_report->progress( _pTracker->_dnlPercent, userData ) ) {
605 _missedDownloads = true;
606 _requiredDls.clear();
607 _dispatcher->cancelAll( _("Cancelled by user."));
608 }
609 }
610
611}
Store and operate with byte count.
Definition ByteCount.h:32
void onRequestProgress(zyppng::NetworkRequest &req, zypp::ByteCount count)
RepoUrl * findUsableMirror(RepoUrl *skip=nullptr, bool allowTainted=true)
Tries to find a usable mirror.
void makeJobUrl(zypp::Url &resultUrl, media::TransferSettings &resultSet)
void onRequestStarted(zyppng::NetworkRequest &req)
void failCurrentJob(const zypp::Pathname &localPath, const std::optional< zypp::Url > &url, media::CommitPreloadReport::Error e, const std::optional< std::string > &errorMessage)
bool taintCurrentMirror()
Taints the current mirror, returns true if a alternative was found.
void onRequestFinished(zyppng::NetworkRequest &req, const zyppng::NetworkRequestError &err)
callback::SendReport< media::CommitPreloadReport > _report
zyppng::Ref< internal::ProgressTracker > _pTracker
std::map< Repository::IdType, RepoDownloadData > _dlRepoInfo
void reportBytesDownloaded(ByteCount newBytes)
void preloadTransaction(const std::vector< sat::Transaction::Step > &steps)
zyppng::NetworkRequestDispatcherRef _dispatcher
long download_max_concurrent_connections() const
static MediaConfig & instance()
static Url rewriteUrl(const Url &url_r, const media::MediaNr medianr)
Replaces media number in specified url with given medianr.
Package interface.
Definition Package.h:34
Combining sat::Solvable and ResStatus.
Definition PoolItem.h:51
Pathname predownloadPath() const
Path where this repo packages are predownloaded.
Definition RepoInfo.cc:597
url_set baseUrls() const
The complete set of repository urls.
Definition RepoInfo.cc:651
IdType id() const
Expert backdoor.
Definition Repository.h:321
sat::detail::RepoIdType IdType
Definition Repository.h:44
SrcPackage interface.
Definition SrcPackage.h:30
Url manipulation class.
Definition Url.h:93
static ZConfig & instance()
Singleton ctor.
Definition ZConfig.cc:940
Typesafe passing of user data via callbacks.
Definition UserData.h:40
bool set(const std::string &key_r, AnyType val_r)
Set the value for key (nonconst version always returns true).
Definition UserData.h:119
Wrapper class for stat/lstat.
Definition PathInfo.h:226
bool isExist() const
Return whether valid stat info exists.
Definition PathInfo.h:286
static ManagedFile asManagedFile()
Create a temporary file and convert it to a automatically cleaned up ManagedFile.
Definition TmpPath.cc:240
static bool authenticate(const Url &url, CredentialManager &cm, TransferSettings &settings, const std::string &availAuthTypes, bool firstTry)
Holds transfer setting.
const std::string & proxy() const
proxy host
void addHeader(std::string &&val_r)
add a header, on the form "Foo: Bar" (trims)
std::multimap< std::string, std::string > HeaderList
std::string alias() const
unique identifier for this source.
@ TRANSACTION_MULTIINSTALL
[M] Install(multiversion) item (
Definition Transaction.h:67
@ TRANSACTION_INSTALL
[+] Install(update) item
Definition Transaction.h:66
WeakPtr parent() const
Definition base.cc:26
static Ptr create()
The NetworkRequestError class Represents a error that occured in.
std::string toString() const
toString Returns a string representation of the error
bool hasError() const
Checks if there was a error with the request.
Definition request.cc:1022
SignalProxy< void(NetworkRequest &req, const NetworkRequestError &err)> sigFinished()
Signals that the download finished.
Definition request.cc:1066
SignalProxy< void(NetworkRequest &req, zypp::ByteCount count)> sigBytesDownloaded()
Signals that new data has been downloaded, this is only the payload and does not include control data...
Definition request.cc:1056
std::string extendedErrorString() const
In some cases, curl can provide extended error information collected at runtime.
Definition request.cc:1014
NetworkRequestError error() const
Returns the last set Error.
Definition request.cc:1006
SignalProxy< void(NetworkRequest &req)> sigStarted()
Signals that the dispatcher dequeued the request and actually starts downloading data.
Definition request.cc:1051
TransferSettings & transferSettings()
Definition request.cc:982
unsigned short a
unsigned short b
void fillSettingsFromUrl(const Url &url, media::TransferSettings &s)
Fills the settings structure using options passed on the url for example ?timeout=x&proxy=foo.
void fillSettingsSystemProxy(const Url &url, media::TransferSettings &s)
Reads the system proxy configuration and fills the settings structure proxy information.
Url clearQueryString(const Url &url)
int rmdir(const Pathname &path)
Like 'rmdir'.
Definition PathInfo.cc:371
int unlink(const Pathname &path)
Like 'unlink'.
Definition PathInfo.cc:705
int assert_dir(const Pathname &path, unsigned mode)
Like 'mkdir -p'.
Definition PathInfo.cc:324
int rename(const Pathname &oldpath, const Pathname &newpath)
Like 'rename'.
Definition PathInfo.cc:747
static const RepoIdType noRepoId(0)
Id to denote Repo::noRepository.
const std::string & asString(const std::string &t)
Global asString() that works with std::string too.
Definition String.h:139
Url details namespace.
Definition UrlBase.cc:58
Easy-to use interface to the ZYPP dependency resolver.
AutoDispose< const Pathname > ManagedFile
A Pathname plus associated cleanup code to be executed when path is no longer needed.
Definition ManagedFile.h:27
Pathname cachedLocation(const OnMediaLocation &loc_r, const RepoInfo &repo_r)
Definition Package.cc:99
media::UrlResolverPlugin::HeaderList headers
RepoInfo repoInfo() const
Repository repository() const
#define zypp_defer
#define _(MSG)
Definition Gettext.h:39
#define DBG
Definition Logger.h:99
#define MIL
Definition Logger.h:100
#define ERR
Definition Logger.h:102