Repository navigation
Database Packaging - #1121
Database Packaging#1121isc-dchui wants to merge 49 commits into
Conversation
isc-jili
left a comment
There was a problem hiding this comment.
Looks great @isc-dchui ! I left some comments!
bba26d8 to
216223e
Compare
isc-kiyer
left a comment
There was a problem hiding this comment.
@isc-dchui few small notes. I love the description of the MR! Could you add some more information for each of the "Key Details"? In particular, for the following:
- Can you explain why generated resources need special treatment? When packaging, wouldn't they be in the routine db already?
- What is the reason for exporting the module manifests and reimporting them? Could that not be accomplished by setting up a global mapping/copying over the IPM globals from the globals db to the routine db during packaging? Then its one less step at deployment time.
|
|
||
| // get metadata from annotations | ||
| set metadata = ..GetPackageMetadata(..Location, name, tag, "", client) | ||
| if (metadata = "") { |
There was a problem hiding this comment.
What is the metadata value if the tag doesn't exist in the tag list? So that it's not equal to ""
There was a problem hiding this comment.
GetAllTags() won't return it so that tag won't be part of the while loop.
set allTagsString = ..GetAllTags(..Location, name, "", client)
set allVersionsList = ..AggregatePlatformVersions($listfromstring(allTagsString, ", "), .aggregatedPlatformVersion)
set pointer = 0
while $listnext(allVersionsList,pointer,moduleVersion) {
...
}
1193db1 to
a366d71
Compare
@isc-kiyer Updated the description with a little more organization clarity! To explicitly answer these,
|
isc-jili
left a comment
There was a problem hiding this comment.
Looks good! @isc-dchui One more Q: are all the resources as part of transitive dependencies supposed to be packaged up too? Since ExportGeneratedResources() CopyNonCompiledResources() and ExportPythonDependencies() seem to be invoked on the root module only
A test case along those lines that would be nice also would be having a dependency contain a Generated="1" resource or a requirements.txt
07873a8 to
e654cba
Compare
|
Fixed the issue where, on install, dependencies wouldn't run through their lifecycle phases. Details are in a new section of the description. |
…g to dependencies of db-pkg
c8626ed to
7d0ea11
Compare
Description
Resolves #986
Adds a second packaging type. Instead of shipping source for the installer to compile,
package-databasecopies the routines database of the namespace it runs in and ships thatIRIS.DATwith the module's non-compiled files and metadata. Installing swaps the file in as the target namespace's routines database.Package contents
The pinned
SystemRequirementsis an exact-match expression, so a package installs only on the IRISmajor.minorthat built it. Packaging replaces an author's own version range and says so.Packaging
package-database <module>andpublish-database <module>force the%IPM.Lifecycle.Databaselifecycle class and run the Package or Publish phase.package-sourceandpackage-studio-projectare added as explicit names for the existing source and studio-project packaging; the latter is deprecated from the start.Packaging copies what the namespace holds; it does not build. The module and its dependencies must already be loaded there. The Compile phase runs first, so a root module whose code is missing fails there rather than shipping without it. The only thing
%Packageloads itself is the root's test resources, under-include-tests.Step 4 matches what
package -export-python-depsdoes, except that it defaults on here. The staging directory is removed on every path.Why packaging dismounts
A copy of a mounted
IRIS.DATreads only what is on disk, and recently written blocks sit in the buffer pool until the write daemon flushes them. On IRIS 2026.1, a class compiled seconds before the copy is missing from it. Packaging normally follows a load by seconds, so it would fail nondeterministically. No per-database flush exists. The instance-wide ones are worse than a short outage of a build namespace.DismountDatabasedoesn't refuse a database that is in use; other processes get<PROTECT>until the remount.SYS.Databaseis only visible from%SYS, and nothing on that path writes output, because output capture can't resolve its handler routine from%SYS.Staging files:
OnStageForDatabasePackageEach resource processor stages the files it will read at install time through a new
%IPM.ResourceProcessor.Abstracthook,OnStageForDatabasePackage(targetDir, .params) As %Status.FileCopy,CPFandPythonWheeloverride it. The default does nothing. A custom processor that reads files from the module root at install time must override it, or those files won't ship.Registry
Database packages use the same OCI tag as a source package of that version. The
com.intersystems.ipm.packaging=databasemanifest annotation is what tells them apart; an absent annotation means source. So a version holds either a source or a database package, not both.Base.%Publishtakes the type from the lifecycle class'sPACKAGINGparameter, not the module's storedPackaging, which records how it was loaded.Installation
install/updatefrom a registry resolve only source packages unless-swap-dbis passed, in which case the named module resolves as a database package. Dependencies always resolve as source, since a database package already carries their code. If only a database package exists, the error says to re-run with-swap-db.load <tgz>applies no filter and prompts before the swap unless-swap-dbis passed.A database install is a module with
Packaging = databaseand anIRIS.DATin its root.LoadNewModuleskipsLoadDependenciesfor it.%Reloaddoes all the work, so%Validate,%Compileand%Activateare no-ops.ValidateBeforeSwaporders its checks so the failure reported is the one worth acting on. It checks the namespace first (IPM mapped in, routines database neither mirrored nor shared), then the package's structure and mapping source databases, then that this is the only database package in the namespace, then orphans. The whole-file SHA-256 runs last, since it reads a potentially multi-gigabyte file.The orphan check compares every other installed module with the outgoing and incoming dependency sets:
IRIS.DAT. It is unconfigured before the swap and its record is removed after it.Why nothing is rolled back after the swap
The swap is a file rename, so it is the last step with a clean undo.
LoadNewModule's transaction is committed as soon as it lands. Rolling back later would pair old module records with new code and couldn't undo CPF merges, installed wheels or copied files. A post-swap failure reports what to do and is fixed forward by re-running the same command from the.tgzor registry.Mappings are applied after that commit because their
Config.Map*locks are held until commit, and a dependency's CPF merge (iris merge) can't wait them out. Inside an outer transaction the commit only folds into it, so mappings are skipped with a warning.Dependency phases
Dependency code arrives compiled, but nothing has run the dependencies' lifecycle, so their CPFs, wheels, copied files, web applications and
<Invoke>s would be dropped.ApplyDependencyResourcesruns each transitive dependency's phases, dependencies before dependents, with its Root pointed atdeps-files/<dep>/.For this tree:
Columns are modules, rows are time:
[1] The root's Initialize runs before the swap, against the old database. Dependencies' Initialize runs after, because their code is inside the incoming
IRIS.DAT.[2] For the root and every dependency at once, before any dependency phase runs.
[3] Only on
update. A fresh install instead seeds update steps as already run (each dependency's in its Activate, the root's inHandleAllUpdateSteps), so a laterupdateruns only newer steps.[4] Re-applies the root's Initialize resources, as described below.
These come from the canonical install chain minus
Reload(no source ships),Compile(already inIRIS.DAT) and*(the refresh checkpoint for the module being installed). Deriving them means a phase added to the chain later isn't silently skipped for dependencies.The root's Initialize ran before the swap, so a dependency's Initialize-phase CPF merge or wheel would override the root's.
ReassertRootInitializeResourcesre-applies the root's afterwards, but only for processor types a dependency actually used, since each CPF merge costs a subprocess.Changes outside the new lifecycle
Storage.Module.%OnValidateObjectderives the lifecycle class from<Packaging>throughBase.GetBaseClassForPackaginginstead of rejecting a mismatch with the default class.Storage.Module.GetDependencyModules/GetDependencyNamesreturn installed transitive dependencies in dependency order from local records only. They reuseConstructInvertedDependencyGraphandGetFlatDependencyListFromInvertedDependencyGraph. The sort gains a fallback for cycles, which installed records can hold (aScope="test"dependency back on the root), instead of looping forever.Base.%Reload's mapping code is extracted intoApplyModuleMappingsso the database install can apply them.CPFandFileCopyeach get a relative-path helper shared by export, install and staging, so packaging stages the files the install reads.%IPM.Utils.Module's database getters useConfig.*.Getand throw instead of dereferencing a possibly nullOpen.GetDatabaseDirectorynormalizes, since the directory, not the name, identifies the file being swapped.ExportPythonDependencieswarns about a missing wheels directory only when the module has arequirements.txt.Pullhashes the downloaded file withComputeSHA256Hexand throws on a failedLinkToFile.Default.Document.OnPhasedrops a block that markedGeneratedresources as handled. No caller reads that flag in the phases where it was set.%IPM.Test.Utils.CreateNamespacetakespEnableEnsemble(default 0) instead of enabling interoperability in every test namespace, which shortens the integration suite. When it is set,^IRIS.Msgis mapped to the namespace's own database so IRIS for Health's read-only HSLIB copy isn't used.Not handled
<Global>data and other content that lives in the globals database (warned).<CPF>that creates the database its own<Mapping>uses: dependency mappings are applied before dependency CPFs run.Testing
Test.PM.Integration.DatabasePackagingcovers:<Invoke>timing, dependency resources applied after the swap, and namespace-relative web applicationsTwo lazily created shared packaging namespaces avoid a create/teardown per test. Tests that mutate their packaging namespace get their own.
Test.PM.Unit.LifecycleDatabasecovers lifecycle derivation, dependency ordering, the dependency phase exclusions, the staging hook and theSystemRequirementstransform.Checklist
mainbranch rebased or merged.zpm test -only) and integration tests (zpm verify -only) pass.