From 98d3459ae110037b07d4cd3919f301ba4910f429 Mon Sep 17 00:00:00 2001 From: Alain Takoudjou <talino@tiolive.com> Date: Wed, 6 Aug 2014 17:57:17 +0200 Subject: [PATCH] Move not related scipts and tests from slapos_cloud to slapos_pdm --- ...ingSubscription_getNewerSofwareRelease.xml | 82 +++++++ ...scription_getUpgradableSoftwareRelease.xml | 106 +++++++++ ...reProduct_getSortedSoftwareReleaseList.xml | 119 ++++++++++ .../TestTemplateItem/testSlapOSPDMSkins.py | 215 +++++++++++++++++- master/bt5/slapos_pdm/bt/revision | 2 +- 5 files changed, 513 insertions(+), 11 deletions(-) create mode 100644 master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getNewerSofwareRelease.xml create mode 100644 master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getUpgradableSoftwareRelease.xml create mode 100644 master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/SoftwareProduct_getSortedSoftwareReleaseList.xml diff --git a/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getNewerSofwareRelease.xml b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getNewerSofwareRelease.xml new file mode 100644 index 000000000..c696b677d --- /dev/null +++ b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getNewerSofwareRelease.xml @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="PythonScript" module="Products.PythonScripts.PythonScript"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_body</string> </key> + <value> <string>hosting_subscription = context\n +portal = context.getPortalObject()\n +\n +software_instance = hosting_subscription.getPredecessorValue()\n +if not software_instance:\n + return None\n +software_release_list = context.SoftwareProduct_getSortedSoftwareReleaseList(\n + software_release_url=software_instance.getUrlString())\n +\n +if not software_release_list:\n + return None\n +latest_software_release = software_release_list[0]\n +if latest_software_release.getUrlString() == software_instance.getUrlString():\n + return None\n +else:\n + return latest_software_release\n +</string> </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string></string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>HostingSubscription_getNewerSofwareRelease</string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getUpgradableSoftwareRelease.xml b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getUpgradableSoftwareRelease.xml new file mode 100644 index 000000000..f66bbf01b --- /dev/null +++ b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/HostingSubscription_getUpgradableSoftwareRelease.xml @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="PythonScript" module="Products.PythonScripts.PythonScript"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_body</string> </key> + <value> <string>"""\n +Check if this hosting subscription is upgradable to the latest version,\n +and return the software release to upgrade with.\n +"""\n +\n +hosting_subscription = context\n +portal = context.getPortalObject()\n +\n +slap_state = [\'start_requested\', \'stop_requested\']\n +\n +if not hosting_subscription.getSlapState() in slap_state:\n + return None\n +\n +source_instance = hosting_subscription.getPredecessorValue()\n +if not source_instance or source_instance.getSlapState() not in slap_state:\n + return None\n +\n +software_release = hosting_subscription.HostingSubscription_getNewerSofwareRelease()\n +if not software_release:\n + return None\n +\n +computer = source_instance.getAggregateValue().getParentValue()\n +if computer.getValidationState() != \'validated\':\n + return None\n + \n +#Find Software Installation\n +software_installation_list = portal.portal_catalog(\n + portal_type="Software Installation",\n + validation_state="validated",\n + url_string=software_release.getUrlString(),\n + default_aggregate_uid=computer.getUid(),\n + #XXX - don\'t select destroyed Software Installation\n + slap_state=\'start_requested\'\n + )\n +# check again slap_state because it might be ignored in previous request!\n +if \'start_requested\' in [software_installation.getSlapState() \\\n + for software_installation in software_installation_list]:\n + return software_release\n +\n +return None\n +</string> </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string></string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>HostingSubscription_getUpgradableSoftwareRelease</string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/SoftwareProduct_getSortedSoftwareReleaseList.xml b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/SoftwareProduct_getSortedSoftwareReleaseList.xml new file mode 100644 index 000000000..df85ce5ed --- /dev/null +++ b/master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/SoftwareProduct_getSortedSoftwareReleaseList.xml @@ -0,0 +1,119 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="PythonScript" module="Products.PythonScripts.PythonScript"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_body</string> </key> + <value> <string encoding="cdata"><![CDATA[ + +from DateTime import DateTime\n +\n +portal = context.getPortalObject()\n +\n +if software_product_reference is None:\n + assert(software_release_url is not None)\n + software_release = portal.portal_catalog.getResultValue(\n + portal_type=\'Software Release\',\n + url_string=software_release_url\n + )\n + if not software_release:\n + return []\n + software_product_reference = software_release.getAggregateReference()\n + if not software_product_reference:\n + return []\n + \n +else:\n + # Don\'t accept both parameters\n + assert(software_release_url is None)\n +\n +product_list = portal.portal_catalog(\n + portal_type=\'Software Product\',\n + reference=software_product_reference,\n + validation_state=\'published\')\n +\n +if not product_list:\n + return []\n +if len(product_list) > 1:\n + raise NotImplementedError(\'Several Software Product with the same title.\')\n +\n +software_release_list = product_list[0].getAggregateRelatedValueList()\n +\n +def sortkey(software_release):\n + publication_date = software_release.getEffectiveDate()\n + if publication_date:\n + if (publication_date - DateTime()) > 0:\n + return DateTime(\'1900/05/02\')\n + return publication_date\n + return software_release.getCreationDate()\n +\n +software_release_list = sorted(\n + software_release_list,\n + key=sortkey, reverse=True,\n + )\n + \n +return [software_release for software_release in software_release_list\n + if software_release.getValidationState() in\n + ["published"]\n + ]\n + + +]]></string> </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string>software_product_reference=None, software_release_url=None</string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>SoftwareProduct_getSortedSoftwareReleaseList</string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/master/bt5/slapos_pdm/TestTemplateItem/testSlapOSPDMSkins.py b/master/bt5/slapos_pdm/TestTemplateItem/testSlapOSPDMSkins.py index f6f354b6f..82a011950 100644 --- a/master/bt5/slapos_pdm/TestTemplateItem/testSlapOSPDMSkins.py +++ b/master/bt5/slapos_pdm/TestTemplateItem/testSlapOSPDMSkins.py @@ -121,6 +121,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): for partition in computer.contentValues(portal_type='Computer Partition'): if partition.getSlapState() == 'free': software_instance.edit(aggregate=partition.getRelativeUrl()) + partition.markBusy() break; def _makeSoftwareProduct(self, new_id): @@ -157,6 +158,15 @@ class TestSlapOSPDMSkins(testSlapOSMixin): ) software_release.publish() return software_release + + def _makeCustomSoftwareRelease(self, new_id, software_product_url, software_url): + software_release = self._makeSoftwareRelease(new_id) + software_release.edit( + aggregate_value=software_product_url, + url_string=software_url + ) + software_release.publish() + return software_release def _makeSoftwareInstallation(self, new_id, computer, software_release_url): software_installation = self.portal\ @@ -247,11 +257,168 @@ class TestSlapOSPDMSkins(testSlapOSMixin): upgrade_decision_module.newContent( portal_type="Upgrade Decision", title="TESTUPDE-%s" % self.new_id) - + def _makeUpgradeDecisionLine(self, upgrade_decision): return upgrade_decision.newContent( portal_type="Upgrade Decision Line", title="TESTUPDE-%s" % self.new_id) + + + def test_getSortedSoftwareReleaseListFromSoftwareProduct(self): + new_id = self.generateNewId() + software_product = self._makeSoftwareProduct(new_id) + release_list = software_product.SoftwareProduct_getSortedSoftwareReleaseList( + software_product.getReference()) + self.assertEqual(release_list, []) + + # published software release + software_release1 = self._makeSoftwareRelease(new_id) + software_release1.edit(aggregate_value=software_product.getRelativeUrl(), + url_string='http://example.org/1-%s.cfg' % new_id, + effective_date=(DateTime() + 5) + ) + software_release1.publish() + software_release2 = self._makeSoftwareRelease(self.generateNewId()) + software_release2.edit(aggregate_value=software_product.getRelativeUrl(), + url_string='http://example.org/2-%s.cfg' % new_id + ) + software_release2.publish() + # 1 released software release, should not appear + software_release3 = self._makeSoftwareRelease(new_id) + self.assertTrue(software_release3.getValidationState() == 'released') + software_release3.edit(aggregate_value=software_product.getRelativeUrl(), + url_string='http://example.org/3-%s.cfg' % new_id + ) + self.tic() + + release_list = software_product.SoftwareProduct_getSortedSoftwareReleaseList( + software_product.getReference()) + self.assertEquals([release.getUrlString() for release in release_list], + ['http://example.org/2-%s.cfg' % new_id, 'http://example.org/1-%s.cfg' % new_id]) + + + def test_getSortedSoftwareReleaseListFromSoftwareProduct_Changed(self): + new_id = self.generateNewId() + software_product = self._makeSoftwareProduct(new_id) + release_list = software_product.SoftwareProduct_getSortedSoftwareReleaseList( + software_product.getReference()) + self.assertEqual(release_list, []) + + # 2 published software releases + software_release2 = self._makeSoftwareRelease(self.generateNewId()) + software_release2.publish() + software_release2.edit(aggregate_value=software_product.getRelativeUrl(), + url_string='http://example.org/2-%s.cfg' % new_id, + effective_date=(DateTime() - 2) + ) + # Newest software release + software_release1 = self._makeSoftwareRelease(new_id) + software_release1.publish() + software_release1.edit(aggregate_value=software_product.getRelativeUrl(), + url_string='http://example.org/1-%s.cfg' % new_id, + effective_date=DateTime() + ) + self.tic() + + release_list = software_product.SoftwareProduct_getSortedSoftwareReleaseList( + software_product.getReference()) + self.assertEquals([release.getUrlString() for release in release_list], + ['http://example.org/1-%s.cfg' % new_id, 'http://example.org/2-%s.cfg' % new_id]) + release_list = software_product.SoftwareProduct_getSortedSoftwareReleaseList( + software_release_url='http://example.org/1-%s.cfg' % new_id) + self.assertEquals([release.getUrlString() for release in release_list], + ['http://example.org/1-%s.cfg' % new_id, 'http://example.org/2-%s.cfg' % new_id]) + + + def test_HostingSubscription_getNewerSofwareRelease(self): + person = self._makePerson(self.new_id) + computer = self._makeComputer(self.new_id) + computer.edit(source_administration_value=person) + software_product = self._makeSoftwareProduct(self.new_id) + oldest_software_url = 'http://example.org/oldest-%s.cfg' % self.new_id + newest_software_url = 'http://example.org/newest-%s.cfg' % self.new_id + + self._makeCustomSoftwareRelease(self.new_id, + software_product.getRelativeUrl(), + oldest_software_url) + self._makeCustomSoftwareRelease(self.generateNewId(), + software_product.getRelativeUrl(), + newest_software_url) + self._makeSoftwareInstallation(self.new_id, computer, oldest_software_url) + + hosting_subscription = self._makeFullHostingSubscription(self.new_id, + oldest_software_url, person) + self.tic() + self.assertEqual(hosting_subscription.HostingSubscription_getNewerSofwareRelease(), + None) + + self._makeFullSoftwareInstance(hosting_subscription, oldest_software_url) + self.tic() + release = hosting_subscription.HostingSubscription_getNewerSofwareRelease() + self.assertEqual(release.getUrlString(), newest_software_url) + + def testHostingSubscription_getUpgradableSoftwareRelease_no_installation(self): + person = self._makePerson(self.new_id) + computer = self._makeComputer(self.new_id) + computer.edit(source_administration_value=person) + self._makeComputerPartitions(computer) + software_product = self._makeSoftwareProduct(self.new_id) + oldest_software_url = 'http://example.org/oldest-%s.cfg' % self.new_id + newest_software_url = 'http://example.org/newest-%s.cfg' % self.new_id + self._makeCustomSoftwareRelease(self.new_id, + software_product.getRelativeUrl(), + oldest_software_url) + self._makeSoftwareInstallation(self.new_id, computer, oldest_software_url) + hs = self._makeFullHostingSubscription(self.new_id, + oldest_software_url, person) + self.tic() + self.assertEqual(hs.HostingSubscription_getUpgradableSoftwareRelease(), + None) + + self._makeFullSoftwareInstance(hs, oldest_software_url) + self._markComputerPartitionBusy(computer, hs.getPredecessorValue()) + self._makeCustomSoftwareRelease(self.generateNewId(), + software_product.getRelativeUrl(), + newest_software_url) + self.tic() + self.assertEqual(hs.HostingSubscription_getUpgradableSoftwareRelease(), + None) + + def testHostingSubscription_getUpgradableSoftwareRelease(self): + person = self._makePerson(self.new_id) + computer = self._makeComputer(self.new_id) + computer.edit(source_administration_value=person) + self._makeComputerPartitions(computer) + software_product = self._makeSoftwareProduct(self.new_id) + oldest_software_url = 'http://example.org/oldest-%s.cfg' % self.new_id + newest_software_url = 'http://example.org/newest-%s.cfg' % self.new_id + self._makeCustomSoftwareRelease(self.new_id, + software_product.getRelativeUrl(), + oldest_software_url) + self._makeSoftwareInstallation(self.new_id, computer, oldest_software_url) + hs = self._makeFullHostingSubscription(self.new_id, + oldest_software_url, person) + + self._makeFullSoftwareInstance(hs, oldest_software_url) + self._markComputerPartitionBusy(computer, hs.getPredecessorValue()) + self._makeCustomSoftwareRelease(self.generateNewId(), + software_product.getRelativeUrl(), + newest_software_url) + self._makeSoftwareInstallation(self.generateNewId(), computer, + newest_software_url) + # software_release should be ignored! + software_release = self._makeSoftwareRelease(self.generateNewId()) + self._makeSoftwareInstallation(self.generateNewId(), + computer, software_release.getUrlString()) + self.tic() + release = hs.HostingSubscription_getUpgradableSoftwareRelease() + self.assertEqual(release.getUrlString(), newest_software_url) + + self.portal.portal_workflow._jumpToStateFor(hs, 'destroy_requested') + self.tic() + self.assertEqual(hs.HostingSubscription_getUpgradableSoftwareRelease(), + None) + def testUpgradeDecision_getComputer(self): computer = self._makeComputer(self.new_id) @@ -811,7 +978,8 @@ class TestSlapOSPDMSkins(testSlapOSMixin): def testComputer_checkAndCreateUpgradeDecision(self): person = self._makePerson(self.new_id) computer = self._makeComputer(self.new_id) - computer.edit(source_administration_value=person) + computer.edit(source_administration_value=person, + allocation_scope="open/public") software_product = self._makeSoftwareProduct(self.new_id) software_release = self._requestSoftwareRelease(self.new_id, software_product.getRelativeUrl()) @@ -831,7 +999,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): upgrade_decision = computer.Computer_checkAndCreateUpgradeDecision() self.assertEqual(len(upgrade_decision), 1) - self.assertEqual(upgrade_decision[0].getSimulationState(), 'confirmed') + self.assertEqual(upgrade_decision[0].getSimulationState(), 'started') computer_aggregate = upgrade_decision[0].UpgradeDecision_getComputer() self.assertEqual(computer_aggregate.getReference(), @@ -843,10 +1011,11 @@ class TestSlapOSPDMSkins(testSlapOSMixin): upgrade_decision2 = computer.Computer_checkAndCreateUpgradeDecision() self.assertEqual(len(upgrade_decision2), 0) - def testComputer_checkAndCreateUpgradeDecision_with_exist(self): + def testComputer_checkAndCreateUpgradeDecision_personal_with_exist(self): person = self._makePerson(self.new_id) computer = self._makeComputer(self.new_id) - computer.edit(source_administration_value=person) + computer.edit(source_administration_value=person, + allocation_scope="open/personal") software_product = self._makeSoftwareProduct(self.new_id) software_release = self._requestSoftwareRelease(self.new_id, software_product.getRelativeUrl()) @@ -857,7 +1026,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): self.tic() upgrade_decision = computer.Computer_checkAndCreateUpgradeDecision()[0] - self.assertEqual(upgrade_decision.getSimulationState(), 'confirmed') + self.assertEqual(upgrade_decision.getSimulationState(), 'planned') software_release3 = self._requestSoftwareRelease(self.generateNewId(), software_product.getRelativeUrl()) @@ -866,10 +1035,36 @@ class TestSlapOSPDMSkins(testSlapOSMixin): upgrade_decision2 = computer.Computer_checkAndCreateUpgradeDecision()[0] self.assertEqual(upgrade_decision.getSimulationState(), 'cancelled') - self.assertEqual(upgrade_decision2.getSimulationState(), 'confirmed') + self.assertEqual(upgrade_decision2.getSimulationState(), 'planned') release = upgrade_decision2.UpgradeDecision_getSoftwareRelease() self.assertEqual(release.getUrlString(), software_release3.getUrlString()) + + def testComputer_checkAndCreateUpgradeDecision_public_with_exist(self): + person = self._makePerson(self.new_id) + computer = self._makeComputer(self.new_id) + computer.edit(source_administration_value=person, + allocation_scope="open/public") + software_product = self._makeSoftwareProduct(self.new_id) + software_release = self._requestSoftwareRelease(self.new_id, + software_product.getRelativeUrl()) + self._makeSoftwareInstallation(self.new_id, + computer, software_release.getUrlString()) + self._requestSoftwareRelease(self.generateNewId(), + software_product.getRelativeUrl()) + self.tic() + + upgrade_decision = computer.Computer_checkAndCreateUpgradeDecision()[0] + self.assertEqual(upgrade_decision.getSimulationState(), 'started') + + self._requestSoftwareRelease(self.generateNewId(), + software_product.getRelativeUrl()) + self.tic() + + upgrade_decision2 = computer.Computer_checkAndCreateUpgradeDecision() + + self.assertEqual(len(upgrade_decision2), 0) + self.assertEqual(upgrade_decision.getSimulationState(), 'started') def testComputer_hostingSubscriptionCreateUpgradeDecision_no_newer(self): @@ -933,7 +1128,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): self.tic() up_decision = computer.Computer_hostingSubscriptionCreateUpgradeDecision()[0] - self.assertEqual(up_decision.getSimulationState(), 'confirmed') + self.assertEqual(up_decision.getSimulationState(), 'planned') self.assertEqual(up_decision.UpgradeDecision_getHostingSubscription().\ getReference(), hosting_subscription.getReference()) @@ -973,7 +1168,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): self.tic() up_decision = computer.Computer_hostingSubscriptionCreateUpgradeDecision()[0] - self.assertEqual(up_decision.getSimulationState(), 'confirmed') + self.assertEqual(up_decision.getSimulationState(), 'planned') # Install the another software release software_release3 = self._requestSoftwareRelease(self.generateNewId(), @@ -983,7 +1178,7 @@ class TestSlapOSPDMSkins(testSlapOSMixin): self.tic() up_decision2 = computer.Computer_hostingSubscriptionCreateUpgradeDecision()[0] - self.assertEqual(up_decision2.getSimulationState(), 'confirmed') + self.assertEqual(up_decision2.getSimulationState(), 'planned') self.assertEqual(up_decision.getSimulationState(), 'cancelled') release = up_decision2.UpgradeDecision_getSoftwareRelease() self.assertEqual(release.getUrlString(), diff --git a/master/bt5/slapos_pdm/bt/revision b/master/bt5/slapos_pdm/bt/revision index d99e90eb9..8580e7b68 100644 --- a/master/bt5/slapos_pdm/bt/revision +++ b/master/bt5/slapos_pdm/bt/revision @@ -1 +1 @@ -29 \ No newline at end of file +30 \ No newline at end of file -- 2.30.9