Question
· Jun 18, 2016

Updating Ensemble business host programatically

Hi. This about  a migration of code from a DEV environment to a PROD environment.

If an (under development) business host is Enabled in DEV environment  and the production class is migrated across environments, this means that the Enabled status of the same business host in PROD would also become enabled (even if it may have been disabled before the update)

I want to migrate the production class, but leave the enabled/disabled status of business hosts the same - so - i make a list of them, upgrade - then go through each one resetting their status to the original setting using Ens.Director.

Problem is- this does not update the XML in the production class, and, if anyone accidentally compiles it in PRD, the bad statues will be re-instated. 

If you change the status of a business host from the management portal, then, i know the class updates.

Is there an API to update the Enabled attribute of a Business host as defined in the XML X DATA block of the production class ? 

I'm aware I can use XSLT, haven't explored how easy that is yet, so, if someone has sample xslt- that would be great too.

Steve

Discussion (11)2
Log in or sign up to continue

Hi,

What I was hoping to do is programatically set the 'default' Enabled/Disabled status of a configuration item,  - ie - editing the XML in the production class.  We do this from the Management Portal so that when a I disable a component, stop the production and re-start it, the component is still disabled.

I don't want to run the risk of messages starting to flow into the production namespace, by starting the production, then, OnStart - (which presumably kicks off after all queues have been setup and business services start 'listening'),  subsequently start disabling some components.

Steve

I reproduced what you're seeing in latest.

If I disable a component via the Production Configuration Page, I see in studio the production class is updated immediately.  It automatically refreshes itself within studio.

When I disable the component using Ens.Director in terminal, the production class is not updated at all.  Even if I close and reopen the production class, it still has the old value.

I took a look at the code and it looks like when settings are modified via the Production Configuration page, method SaveToClass in class Ens.Config.Production gets called (near the end of method SaveSettingsToServer in class EnsPortal.ProductionConfig).  When Ens.Director method EnableConfigItem is called, there is no call to SaveToClass.  I just tested adding that call to the method EnableConfigItem and it worked for me.

So you could add code after your call to Ens.Director method EnableConfigItem to open the Ens.Config.Production object and call the SaveToClass method, passing the Ens.Config.Item object for the component that was just enabled/disabled.  You can take a look at the EnableConfigItem method to see how it gets the Ens.Config.Production object and the Ens.Config.Item object.

Hi 

Thanks Eduard for trying and for John's comments.

Brendan - Actually... - I tried using the SaveToClass() method as you described before I posted my question to the community, but, it my initial tests showed it deleting the entire contents of my XDATA block.  I was not confident that was the way to go - hence the question, thinking there might have been another API.

However - spurred on again today by yourself coming to the same idea I already had, I decided to give it another look - and - I have solved the problem.

For all - I want to re-iterate - this is how to set the Enabled configuration status of a business host by modifying the XML in the production class's XDATA block.  For run-time enabling/disabling, use EnableConfigItem method of Ens.Director.

If the enabled status changes you want to make are to the production's configuration, then - here is the solution:

 

/// productionName = Package.Classname
/// 
Items(ConfigItem)=startupStatus 
///

/// ConfigItem = must contain the fully qualified reference of: ConfigName|ClassNameOfComponent
///                            due to config items, that can be defined as the same name multiple times in a production
/// startupStatus = 1 or 0 (true or false respectively)
/// 
ClassMethod UpdateClassEnabledStatuses(productionName As %String, ByRef Items As %String) As %Status
{
set tSC=$$$OK 

Try {
set statProdRunning=##class(Ens.Director).IsProductionRunning(productionName)
if statProdRunning=1 {
  // Stop Production
  set tSC=##class(Ens.Director).StopProduction()
  if $$$ISERR(tSC) {
    write !,"Unable to stop production. Exiting without changes."
    quit 
   }
}

// take out an exclusive lock on the class to avoid being edited remotely.
lock +^oddDEF(productionName)#"E":5
if '$t {
 write !,"Unable to lock production class. Exiting without changes."
 quit 
}

// iterate through items.
set objProd=##class(Ens.Config.Production).%OpenId(productionName)
if '$IsObject(objProd) {
  write !,"Production configuration for production: "_productionName_". couldn't be found. Existing without changes."
  quit
}
set ci=""
for  {
  set ci=$order(Items(ci)) quit:ci=""
  set fqConfigItem=productionName_"||"_ci

  // change the status in the class.
  set objConfigItem=objProd.FindItemByConfigName(fqConfigItem)

  set objConfigItem.Enabled=Items(ci)
  write !,"Updating item: "_$piece(ci,"|")_" ("_$piece(ci,"|",2)_")"_" to: "_$select(Items(ci):"True",1:"False")
 
  set err=objProd.SaveToClass(objConfigItem) if $$$ISERR(err) write " - Failed" continue
  set err=objProd.%Save() if $$$ISERR(err) write " - Failed" continue

}

// release exclusive lock the class
lock -^oddDEF(productionName)#"E"

// Reset the running status of the production
if statProdRunning=1 {
  do ##class(Ens.Director).StartProduction(productionName)
}

catch exceptionvar {
  lock -^oddDEF(productionName)#"E"
  set tSC=exceptionvar.AsStatus()
}
  quit tSC
}
 

 

 

Steve.

Hi Steve,

Thanks for your help on this. This is exactly what I was looking for.

I have now successfully integrated this into my code release procedure such that the state of production interfaces are now maintained and are now also saved back into the production class definition.

My procedure is basically as follows:

1. Save the state of production interfaces

2. Load and compile the new code set

3. Retrieve previously stored interface states

4. Save interfaces back into the production class definition.

5. Force an update of the production to restore the previous running state of all interfaces.

Doing this ensures that any new interfaces in the production class are picked up, the state of any existing interfaces is maintained, the production class definition correctly matches the state of all interfaces, and no manual intervention is required to update the production state (i.e. via the management portal).

As a side note I have discovered that the production class definition must be compiled in all namespaces that use it in order for changes to be picked up. We have a common application set in a separate database which is referenced by multiple namespaces so I have to switch to the appropriate namespace and compile all code relevant to that namespace locally. This differs from our standard Cache installations where we have the same situation but in that case the code only needs to be compiled in the application namespace and all other namespaces that use it can happily reference it.

Finally, the difficulties we have encountered in our code release procedure has highlighted a number of questions for us in relation to the Ensemble system design and in particular the role of the production class definition.

While a great number of settings can be stored locally for an Ensemble instance via the use of System Default settings (file paths, IP addresses, ports etc.) the Enabled/Disabled state of an interface is part of the production class definition. It makes sense to us that this really should be stored as data.

In fact the whole production class definition is really just a set of configuration data and we question why this must be maintained as a class definition rather than just stored as a set of data elements to be referenced.

The current design causes difficulties particularly when there is concurrent development occurring. We are currently still in the fairly early stages of our integration of Ensemble and as such we are bringing new interfaces on at a regular rate and these are being worked on by multiple developers. The issue we encounter is that because the interface definitions are all stored in the production class, if we want to deploy a new interface ahead of another we have to remember to temporarily remove the interface we are not deploying. This causes interruptions/delays in development and there is the risk that if we forget to do this then we could break the system we are deploying to as the production class may contain an interface for which there is no underlying supporting code. This has actually already happened to us. 

If the interface definitions were merely a set of data elements it would be very easy to segregate the implementation of one interface from another and we would not experience the issues we have with concurrent development.

We would be interested to hear how other customers manage this situation and  would be eager to hear from anyone who has some inspiration or experience in working with Ensemble on a large scale and just how you manage these difficulties and we look forward to engaging with Intersystems further on this also.

Regards,

Jo

I agree, the Production class is a major problem for Configuration Management. In the past we've tried System Defaults and found it very awkward to use. In the last project we started with separate classes for dev, test and live. The updates to test and live versions were done in a release preparation namespace, with comparisons done to ensure they were in step, as mentioned earlier. Then a release was built from that and installed in test slot and later in live slot.

But it all got harder and harder to handle, and when the system went fully live we stopped doing full releases and went over to releasing only changed classes, etc. and never releasing the production classes. Now the problem is that changes to the production have to be done manually via the front end. Fortunately, there are a lot less of these now.

Regards,

Mike

In later versions, individual components and their settings can be moved with the Deployment feature in Ensemble.  In your staging instance on the production configuration page, select the component you want to move, switch to the 'Actions' tab, and click the 'Export' button.  This will allow you to export the code associated with the component along with a .ptd file which contains all of the settings.  Then, in the production instance, from the main page of the management portal, go to Ensemble->Manage->Deployment Changes->Deploy and select the xml file you just exported.  This will allow you to import the component and all the settings into the production instance without changing anything else in the production class.

Out of interest, are all other settings of the production's configuration items identical between DEV and PROD? Or are you using System Defaults successfully to ensure that, say, hostname/port of outbound target system is different in DEV versus in PROD?

Many Ensemble sites that use our Deltanji source control tool to propagate code from DEV to PROD choose to exclude the production class itself from that workflow. Some maintain multiple variants of the class, e.g. one for DEV and another for PROD, versioning each variant only within its own environment, and using a diff tool such as Beyond Compare (which Deltanji integrates with) to propagate applicable production changes from DEV to PROD.

Glad to hear that System Defaults are working for them. My main gripe with that mechanism is how cumbersome Portal's UI is for making the setting in the right place. Added to that, it's just too easy for someone to adjust a setting directly, filing the new setting into the production class where it (a) overrides the System Default and (b) propagates when the class gets transferred.