This part of the Rose user guide walks you through writing macros to upgrade application configurations to newer metadata versions.
Upgrade macros are intended to keep application configurations in sync with changes to application inputs e.g. from new code releases.
You should already be familiar with using rose app-upgrade (see the Upgrade Usage tutorial) and the concepts in the reference.
In this example, we'll be upgrading a boat on a desert island.
Create a new directory called make-boat-app somewhere - e.g. in your homespace - containing a rose-app.conf file with the following content:
meta=make-boat/0.1 [namelist:materials] hollow_tree_trunks=1 paddling_twigs=1
This creates an app config that configures our simple boat (a dugout canoe). It references a meta-flag (for which metadata is unlikely to already exist), made up of a category (make-boat) at a particular version (0.1). The meta flag is used by Rose to locate a configuration metadata directory.
Make sure you're using make-boat and not make_boat - the hyphen makes all the difference!
It's important to note that the version in the meta flag doesn't have to be numeric - it could be vn0.1 or alpha or Crafty-Canoe.
We need to create some metadata to make this work.
We need a rose-meta/ directory somewhere, to store our metadata - for the purposes of this tutorial it's easiest to put in in your homespace, but the location does not matter.
Create a rose-meta/make-boat/ directory in your homespace:
mkdir -p ~/rose-meta/make-boat/
This is the category (also called command) directory for the metadata, which will hold sub-directories for actual configuration metadata versions (each containing a rose-meta.conf file, etc).
N.B. Configuration metadata would normally be managed by whoever manages Rose installation at your site.
We know we need some metadata for the 0.1 version, so create a 0.1/ subdirectory under rose-meta/make-boat/:
mkdir -p ~/rose-meta/make-boat/0.1/
We'll need a rose-meta.conf file there too, so create an empty one in the new directory:
touch ~/rose-meta/make-boat/0.1/rose-meta.conf
We can safely say that our two namelist inputs are essential for the construction and testing of the boat, so we can paste the following into the newly created rose-meta.conf file:
[namelist:materials=hollow_tree_trunks] compulsory=true values=1 [namelist:materials=paddling_twigs] compulsory=true range=1: type=integer
So far, we have a normal application configuration which references some metadata, somewhere, for a category at a certain version.
Let's make another version to upgrade to.
The next version of our boat will have outriggers to make it more stable. Some of the inputs in our application configuration will need to change.
Our application configuration might need to look something like this, after any upgrade (don't change it yet!):
meta=make-boat/0.2 [namelist:materials] hollow_tree_trunks=1 misc_branches=4 outrigger_tree_trunks=2 paddling_branches=1
It looks like we've added the inputs misc_branches, outrigger_tree_trunks and paddling_branches. paddling_twigs is no longer there (now redundant), so we can remove it from the configuration when we upgrade.
Let's create the new metadata version, to document what we need and don't need.
Create a new subdirectory under make-boat/ called 0.2/ containing a rose-meta.conf file that looks like this:
[namelist:materials=hollow_tree_trunks] compulsory=true values=1 [namelist:materials=misc_branches] compulsory=true range=4: [namelist:materials=paddling_branches] compulsory=true range=1: type=integer [namelist:materials=outrigger_tree_trunks] compulsory=true values=2
You can check that everything is OK so far by
changing directory to the make-boat/
directory and running find
- it should
look something like:
. ./0.1 ./0.1/rose-meta.conf ./0.2 ./0.2/rose-meta.conf
We now want to automate the process of updating our app config from make-boat/0.1 to the new make-boat/0.2 version.
Upgrade macros are invoked through a Python module, versions.py, that doesn't live with any particular version metadata - it should be present at the root of the category directory.
Create a new file versions.py under make-boat/ (~/rose-meta/make-boat/versions.py). We'll add a macro to it in a little bit.
Upgrade macros are Python objects with a BEFORE_TAG (e.g. "0.1") and an AFTER_TAG (e.g. "0.2"). The BEFORE_TAG is the 'start' version (if upgrading) and the AFTER_TAG is the 'destination' version.
When a user requests an upgrade for their
configuration (e.g. by running rose
app-upgrade
), the versions.py
file will be searched for a macro whose
BEFORE_TAG matches the
meta=... version.
For example, for our meta=make-boat/0.1 flag, we'd need a macro whose BEFORE_TAG was "0.1".
When a particular upgrade macro is run, the version in the app configuration will be changed from BEFORE_TAG to AFTER_TAG (e.g. meta=make-boat/0.1 to meta=make-boat/0.2), as well as making other changes to the configuration if needed, like adding/removing the right variables.
If the user wanted to upgrade across multiple versions - e.g. 0.1 to 0.4 - there would need to be a chain of objects whose BEFORE_TAG was equal to the last AFTER_TAG, ending in an AFTER_TAG of 0.4.
We'll cover multiple version upgrading later in the tutorial.
Upgrade macros are bits of Python code that essentially look like this:
class Upgrade272to273(rose.upgrade.MacroUpgrade): """Upgrade from 27.2 to 27.3.""" BEFORE_TAG = "27.2" AFTER_TAG = "27.3" def upgrade(self, config, meta_config=None): """Upgrade the application configuration (config).""" # Some code doing something to config goes here. return config, self.reports
They are sub-classes of a particular class, rose.upgrade.MacroUpgrade, which means that some of the Python functionality is done 'under the hood' to make things easier.
You shouldn't need to know very much Python to get most things done.
Paste the following into your versions.py file:
import rose.upgrade class MyFirstUpgradeMacro(rose.upgrade.MacroUpgrade): """Upgrade from 0.1 (Canonical Canoe) to 0.2 (Outrageous Outrigger).""" BEFORE_TAG = "0.1" AFTER_TAG = "0.2" def upgrade(self, config, meta_config=None): """Upgrade the boat!""" # Some code doing something to config goes here. return config, self.reports
This is already a functional upgrade macro - although it won't do anything. Note that the name of the class (MyFirstUpgradeMacro) doesn't need to be related to the versions - the only identifiers that matter are the BEFORE_TAG and the AFTER_TAG.
We need to get the macro to do the following:
We can use the nice API provided to express this in Python code - replace the # Some code doing something... line with:
self.add_setting(config, ["namelist:materials", "misc_branches"], "4") self.add_setting( config, ["namelist:materials", "outrigger_tree_trunks"], "2") self.add_setting( config, ["namelist:materials", "paddling_branches"], "1") self.remove_setting(config, ["namelist:materials", "paddling_twigs"])
This changes the app configuration (config) in the way we want, and (behind the scenes) adds some things to the self.reports list mentioned in the return config, self.reports line. Note that when we add options like misc_branches, we need values to add them with - what they are will often be a matter of judgement.
However, even if they appear to be numeric inputs, values should always be entered as strings ("1" rather than 1).
The methods self.add_setting
and
self.remove_setting
will provide a
default message to the user about the change (e.g.
"Added X with value Y"), but you can
customise them to add your own by passing an
info 'keyword argument' like this:
self.add_setting( config, ["namelist:materials", "outrigger_tree_trunks"], "2", info="This makes it into a trimaran!")
If you want to, try adding your own messages.
Our upgrade macro will now work - change directory to the application directory and run:
rose app-upgrade --meta-path=~/rose-meta/
--meta-path equals the path to the rose-meta/ directory you created - as this path isn't configured in the site/user configuration, we need to set it manually. This won't normally be the case for users, if the metadata is centrally managed.
This should display some information about the
current and available versions - see the help by
running rose help app-upgrade
.
Let's upgrade to 0.2. Run:
rose app-upgrade --meta-path=~/rose-meta/ 0.2
This should provide you with a summary of changes (including any custom messages you may have added) and prompt you to accept them. Accept them and have a look at the app config file - it should have been changed accordingly.
For relatively straightforward changes like the one above, we can configure a macro to apply patches to the configuration without having to write setting-specific Python code.
We'll add a rudder option for our 0.3 version, with a namelist:materials=l_rudder_branch.
Create a 0.3 directory in the same way that you created the 0.1 and 0.2 metadata directories. Add a rose-meta.conf file that looks like this:
[namelist:materials=hollow_tree_trunks] compulsory=true values=1 [namelist:materials=l_rudder_branch] compulsory=true type=logical [namelist:materials=misc_branches] compulsory=true type=integer range=4: [namelist:materials=outrigger_tree_trunks] compulsory=true values=2 [namelist:materials=paddling_branches] compulsory=true range=1: type=integer
We need to write another macro in versions.py - append the following code:
class MySecondUpgradeMacro(rose.upgrade.MacroUpgrade): """Upgrade from 0.2 (Outrageous Outrigger) to 0.3 (Amazing Ama).""" BEFORE_TAG = "0.2" AFTER_TAG = "0.3" def upgrade(self, config, meta_config=None): """Upgrade the boat!""" self.act_from_files(config) return config, self.reports
The self.act_from_files line tells the macro to look for patch configuration files - two files called rose-macro-add.conf and rose-macro-remove.conf, under an etc/BEFORE_TAG/ subdirectory - in our case, ~/rose-meta/make-boat/etc/0.2/.
Whatever is found in rose-macro-add.conf will be added to the configuration, and whatever is found in rose-macro-remove.conf will be removed. If the files don't exist, nothing will happen.
Let's configure what we want to happen. Create a directory ~/rose-meta/make-boat/etc/0.2/, containing a rose-macro-add.conf file that looks like this:
[namelist:materials] l_rudder_branch=.true.
Note that if a rose-macro-add.conf setting is already defined, the value will not be overwritten. In our case, we don't need a rose-macro-remove.conf file.
Go ahead and upgrade the app configuration to 0.3, as you did before.
The rose-app.conf should now contain the new option, l_rudder_branch.
The Upgrade Macro API gives us quite a bit of power without having to write too much Python.
For our 1.0 release, we want to add sailing gear - we need the number of misc_branches to be at least 6, and add a sail_canvas_sq_m option.
We may want to issue a warning for a deprecated option (paddle_branches) so that the user can decide whether to remove it.
Create a ~/rose-meta/make-boat/1.0 directory, with this content in a rose-meta.conf file:
We need to write a macro that reflects these changes.
We need to start with appending the following code to versions.py:
class MyMoreComplexUpgradeMacro(rose.upgrade.MacroUpgrade): """Upgrade from 0.3 (Amazing Ama) to 1.0 (Tremendous Trimaran).""" BEFORE_TAG = "0.3" AFTER_TAG = "1.0" def upgrade(self, config, meta_config=None): """Upgrade the boat!""" # Some code doing something to config goes here. return config, self.reports
We already know how to add an option, so replace # Some code going here... with self.add_setting(config, ["namelist:materials", "sail_canvas_sq_m"], "5")
To perform the check/change in the number of misc_branches, we can insert the following lines after the one we just added:
branch_num = self.get_setting_value( config, ["namelist:materials", "misc_branches"]) if branch_num.isdigit() and float(branch_num) < 6: self.change_setting_value( config, ["namelist:materials", "misc_branches"], "6")
This extracts the value of
misc_branches (as a string!) and if the
value represents a positive integer that is less than
6, changes it to "6". It's good practice
to guard against the possibility that a user might
have set the value to a non-integer representation
like 'many' - if we don't do this, the
macro may crash out when running things like
float
.
In a similar way, to flag a warning, insert:
paddles = self.get_setting_value( config, ["namelist:materials", "paddling_branches"]) if paddles is not None: self.add_report("namelist:materials", "paddling_branches", paddles, info="Deprecated - probably not needed.", is_warning=True)
This calls self.add_report if the option paddling_branches is present. This is a method that notifies the user of actions and issues by appending things to the self.reports list which appears on the return ...line.
Run rose app-upgrade --meta-path=~/rose-meta/ 1.0 to see the effect of your changes. You should see a warning message for namelist:materials=paddling_branches as well.
We've kept in step with the metadata by upgrading incrementally, but typically users will need to upgrade across multiple versions. When this happens, the relevant macros will be applied in turn, and their changes and issues aggregated.
Turn back the clock by reverting your application configuration to look like it was at 0.1:
meta=make-boat/0.1 [namelist:materials] hollow_tree_trunks=1 paddling_twigs=1
Run rose app-upgrade --meta-path=~/rose-meta/ in the application directory. You should see the available versions to upgrade to - let's choose 1.0. Run:
rose app-upgrade --meta-path=~/rose-meta/ 1.0
This should aggregate all the changes that our macros make - if you accept the changes, it will upgrade all the way to the 1.0 version we had before.
For more information, see the Upgrade Macro API Reference and the general Macro API Reference.