Update ArcGIS Server Services after SQL Server Instance Change

6979
31
06-09-2022 10:09 PM

Update ArcGIS Server Services after SQL Server Instance Change

You may need to migrate your SQL Server geodatabase to a new server.  For example, say you’re upgrading from SQL Server 2016 to SQL Server 2019, and the 2019 instance is on a new server.  The geodatabase migration can be easy enough with a database backup/restore, but what about updating your ArcGIS Server services?  If the services are referencing data from your enterprise geodatabase, the services will use the SQL instance name.  To fix this, you can re-map the data sources in your ArcGIS Pro Projects and/or MXDs, then republish the services.  However, what if you have dozens and dozens of services?  This could be extremely time consuming.  Instead, you can use the workflow below to update the services for you.

Scenario:

A SQL Server instance is being upgraded from 2016 to 2019.  The 2016 instance resides on server PS0014736, and the new 2019 instance resides on server PS0014737.  Services referencing data from the GISDATA geodatabase on PS0014736 have been published to ArcGIS Server:

JakeSkinner_0-1654837338241.png

 

 

Below is the workflow to follow so services do not have to be republished.

Workflow:

Before proceeding with the below steps, create a backup of ArcGIS Server, and make a copy of the \arcgiserver\config-store and \arcgisserver\directories\arcgissystem\arcgisinput directories.

1.  Restore the GISDATA geodatabase to the new SQL Server 2019 instance on PS0014737.

2.  Register the GISDATA geodatabase from the new SQL Server 2019 instance (PS0014737) as a Data Store

JakeSkinner_1-1654837338267.png

3.  Log into ArcGIS Server Administrator to get the DSIDs of the previous (PS0014736) and new (PS0014737) Data Store connections

  1. Navigate to data > items > enterpriseDatabases
  2. Under Child Items, click on each child item for the data store connections:

           

JakeSkinner_2-1654837338280.png

c.  Copy the ID value.  This is the DSID

           

JakeSkinner_3-1654837338286.png

 

4.  Stop the ArcGIS Server service

5.  Update the # Variables in the below script

Variable Name

Description

arcgisInputDirectory

ArcGIS Server arcgisinput directory

arcgisConfigStoreDirectory

ArcGIS Server config-store directory

previousInstance

Previous SQL Server Instance

newInstance

New SQL Server Instance

previousDSID

Previous SQL Server Instance DSID

newDSID

New SQL Server Instance DSID

tempDirectory

Temporary Director to write files to

 

 

 

 

 

 

 

 

 

import arcpy, os, sys, zipfile, json, shutil, re
from zipfile import ZipFile

# Variables
arcgisInputDirectory = r'C:\arcgisserver\directories\arcgissystem\arcgisinput'      # ArcGIS Server arcgisinput directory
arcgisConfigStoreDirectory = r'C:\arcgisserver\config-store\services'               # ArcGIS Server config-store directory
previousInstance = 'PS0021465'                                                      # Previous SQL Server instance
newInstance = 'PS0021475'                                                           # New SQL Server Instance
# named instance would be r'PS0021465\\\\gis'
previousDSID = '09f639cc-9c27-4cf8-be12-0645876b59de'                                   # Previous SQL Server Instance DSID
newDSID = 'c607988a-7494-472e-a590-26c200c7d2b8'                                        # New SQL Server Instance DSID
tempDirectory = arcpy.env.scratchFolder                                             # Temp Directory


# Function to zip MSD
def zipMSD(extractLocation, tempDirectory):
    '''Function to zip directory to create new MSD'''

    newMSDFile = os.path.join(tempDirectory, f"{msdFileName}")
    with zipfile.ZipFile(newMSDFile, 'w', zipfile.ZIP_DEFLATED) as msdzip:
      length = len(extractLocation)

      for root, dirs, files in os.walk(extractLocation):
          folder = root[length:] # path without "parent"
          for file in files:
              msdzip.write(os.path.join(root, file), os.path.join(folder, file))

    return newMSDFile

# Function to update MSD
def updateMSD(service, xmlJSONFile):
    servicesList.append(service)

    # Open XML/JSON File and replace DSID
    print("\tReading file and updating")
    with open(xmlJSONFile, 'r') as file:
        filedata = file.read()
        filedata = filedata.replace(previousDSID, newDSID)
        if re.search(previousInstance, filedata, re.IGNORECASE):
            filedata = re.sub(rf'\b{previousInstance}\b', newInstance, filedata, flags=re.IGNORECASE)
    # Write to new XML/JSON File
    print("\tWriting new file")

    with open(xmlJSONFile, 'w') as file:
        file.write(filedata)

    # Zip directory to create updated MSD
    print("\tCreating new updated MSD file")
    zipMSD(extractLocation, tempDirectory)

    # Replace original MSD file with updated MSD
    print("\tReplacing original MSD file")
    msdFileLocation = os.path.dirname(msdFile)
    shutil.move(os.path.join(tempDirectory, msdFileName),
                os.path.join(msdFileLocation, msdFileName))

# Create list of services that are updated
servicesList = []

# Iterate arcgisinput directory to update MSD file
for root, dirs, files in os.walk(arcgisInputDirectory):
    for name in files:
        if '.msd' in name:
            msdFileName = name
            service = root.split('.')[0].split('\\')[-1]
            msdFile = os.path.join(root, name)
            extractLocation = os.path.join(tempDirectory, service)
            os.mkdir(extractLocation)
            with ZipFile(msdFile, 'r') as zipObj:
                # Extract all the contents of MSD file in temp directory
                print(f'\nExtracting {service} file to temp directory')
                zipObj.extractall(extractLocation)
            # If published from ArcGIS Pro
            if 'p20' in root or 'p30' in root:
                mapDirectory = os.path.join(extractLocation, 'map')
                # Update XML File
                for root, dirs, files in os.walk(mapDirectory):
                    for name in files:
                        if 'map.xml' not in name and '.json' not in name:
                            xmlFile = os.path.join(root, name)
                            updateMSD(service, xmlFile)
                        elif '.json' in name:
                            for root, dirs, files in os.walk(extractLocation):
                                for name in files:
                                    if '.json' in name:
                                        jsonFile = os.path.join(root, name)
                                        updateMSD(service, jsonFile)

            # If published from ArcMap
            elif 'v101' in root:
                mapDirectory = os.path.join(extractLocation, 'layers')
                # Update XML File
                for root, dirs, files in os.walk(mapDirectory):
                    for name in files:
                        if 'layers.xml' not in name:
                            servicesList.append(service)
                            xmlFile = os.path.join(root, name)
                            # Open XML File and replace DSID
                            print("\tReading XML file and updating")
                            with open(xmlFile, 'r') as file:
                                filedata = file.read()
                                filedata = filedata.replace(previousDSID, newDSID)
                            # Write to new XML File
                            print("\tWriting XML file")
                            with open(xmlFile, 'w') as file:
                                file.write(filedata)

                            # Zip directory to create updated MSD
                            print("\tCreating new updated MSD file")
                            zipMSD(extractLocation, tempDirectory)

                            # Replace original MSD file with updated MSD
                            print("\tReplacing original MSD file")
                            msdFileLocation = os.path.dirname(msdFile)
                            shutil.move(os.path.join(tempDirectory, msdFileName),
                                        os.path.join(msdFileLocation, msdFileName))

            # Remove extracted content in temp directory
            print("Removing extracted content")
            shutil.rmtree(extractLocation)

# Update manifest.json
for service in servicesList:
    for root, dirs, files in os.walk(arcgisConfigStoreDirectory):
        for dir in dirs:
            if dir in [f'{service}.MapServer', f'{service}.FeatureServer']:
                serviceDirectory = os.path.join(root, dir)
                for root, dirs, files in os.walk(serviceDirectory):
                    for name in files:
                        # Copy manifest JSON file
                        if name == 'manifest.json':
                            manifestJSON = os.path.join(root, name)
                            copyManifestJSON = os.path.join(tempDirectory, name)
                            shutil.copyfile(manifestJSON, copyManifestJSON)

                            # Read JSON file
                            with open(copyManifestJSON) as jsonFile:
                                data = jsonFile.read()

                            # If previous instance found in json, replace with new instance
                            if re.search(previousInstance, data, re.IGNORECASE):
                                print(f"\nUpdating {service} manifest.json with new instance info")
                                data = re.sub(rf'\b{previousInstance}\b', newInstance, data, flags=re.IGNORECASE)
                                with open(copyManifestJSON, 'w') as jsonFile:
                                    jsonFile.write(data)

                                print("\tReplacing original manifest.json")
                                shutil.copyfile(copyManifestJSON, manifestJSON)

                            # Remove manifest.json in temp directory
                            os.remove(copyManifestJSON)

print("Finished")

 

 

 

 

 

 

 

 

6.  Execute the script

7.  After the script executes, start the ArcGIS Server service

 

Results

The services should now reference the new SQL Server instance (PS0014737):

JakeSkinner_4-1654837338327.png

 

To further demonstrate the service updated, parcels were deleted (northeast section) in the new instance before the script was executed.  Shown below is a screen shot of the service before the script was executed and afterwards:

Service referencing original instance (PS0014736):

JakeSkinner_5-1654837338684.png

 

Service referencing new instance (PS0014737):

JakeSkinner_6-1654837339177.png

 

Video

A video of the workflow can be found at the following link.

Attachments
Comments

I asked Esri for help with this yesterday so this extremely good timing for me. This is far more detailed information.  I only regret I can't give more than 1 kudo.

 

@Brian_Wilson let me know how the script works out for you.  

I ran through it in a debugger and tested.

Pretty interesting things going on down deep in the arcgisinput folder.

The re.search at line 127 and the re.sub would be a big problem for me since the current deployment is single machine and SQL Server runs in the same container. So replacing "cc-gis" with "cc-newserver" will also replace all strings referencing the other components, breaking everything.

Our new deployment will have "cc-gis" and other machines like "cc-gisserver" and "cc-gisdatastore". 

I would need to use a more complete regular expression to avoid a problem. I have not found any manifest files directly referencing the database server so I cannot construct a proper regex. I think it might be using "sde.default" and mapping in the server elsewhere??? I do see references in the MSD files to the ID.

The Esri support guy told me I can change my SQL Server instance by creating an SDE connection doc that points at the new server and then import it on this screen. I will be trying that soon. What could possibly go wrong? I am thinking that if this updates the connection without changing the ID then none of the MSD files should need to change.

BrianWilson7_0-1654972150326.png

I would have made a terrible open heart surgeon. "Wait wait, we need to make a full back up first, and then we need to stop the patient while we do this! Let's get a snapshot and have a virtual copy of the patient ready in case this one fails." (I recall there is a John Varley story along these lines out there.)

 

ALL DONE -- follow up -- I did what I mentioned - changed the connection in Server Manager and everything is now working with the new database. My desktop users will need to adjust their MXD and APRX files but the web sites jumped over without a hiccup.

@Brian_Wilson you are right, updating the Data Store connection file updated the data source of the services.  Originally, I tried this with a customer and we both thought the connections were not updated due to Service Workspaces in Server Manager showing the original instance information (i.e. screen shot in the Results section of the document).  The workflow above will update that information.

The replace option on line 127 will only update the manifest.json for a service that contained the previousDSID value.  It will not crawl through every file in the config-store directory/sub-directory updating occurrences of the previous SQL instance.

The replace option on line 127 will only update the manifest.json

The problem is not that it will update too many manifests, it's that it's doing a global string replace in the manifest. I tested it to confirm this. In my case since all my servers start with "cc-gis" and the original database server was called "cc-gis", the script will change every occurrence of "cc-gis" to "cc-sqlseserver", so for example, "cc-gisdatastore" will be changed to "cc-sqlserverdatastore" when it should not be changed at all because it's the datastore not the sql server.

It should be possible to construct a better regular expression that only finds the SQL instance. I stopped working on it when the other method worked, else I would contribute an updated regular expression.

I'm just worried someone might try the script without enough testing and break things.

@Brian_Wilson good call on the re.sub expression.  I've updated the script to only update whole words.

Hi Jake,

I followed the steps listed above and ran the script.  The python log cycled through all of the services and indicated successful completion.  However, upon verifying, it didn't appear to actually have repointed anything toward the new DB.  As in the above case, we are also trying to migrate our Enterprise DB from off the same machine as ArcGIS Server, where it currently resides, onto its own dedicated machine.  Any idea on what might be going wrong?

Thanks,

Greg

Specs:  ArcGIS Enterprise 10.9; SQL Server 2016 (Windows); ArcGIS Pro 2.9.3

@GregCarlino2 did you stop the ArcGIS Server service before executing the script?

Yes, we stopped ArcGIS Server before running. 

I noticed that there are more comments for certain layers than others, which seemed peculiar.  For example, for the majority of layers, it simply wrote:

Extracting Administrative_Boundary_Layers_050922 file to temp directory
Removing extracted content

Extracting Census_Tracts_2020 file to temp directory
Removing extracted content

Yet, for a handful of layers, it seemed to add more comments:


Extracting Subdivisions_061721 file to temp directory
Reading XML file and updating
Writing XML file
Creating new updated MSD file
Replacing original MSD file
Reading XML file and updating
Writing XML file
Creating new updated MSD file
Replacing original MSD file
Removing extracted content

This also seemed to correspond to whether each service's Workspace Properties were updated within ArcGIS Server Manager.  Yet, even if the properties updated to the new server, they seemed to reflect this change in label only.  All services ceased to load, once we shut down the outgoing SQL Server DB service to confirm, save for one layer that I manually overwrote from ArcGIS Pro, sourcing the new DB, as a sort of control example.

@GregCarlino2 you can update the existing Data Store connection to the new geodatabase connection file and the services will update, however the labels in Server Manager will not.  

As for why the workflow is not working, I can't say for sure.  I would need to further trouble shoot.

Hi Jake,

One potential cause of our failure might be concerning the correct DSID of the previous Data Store, as it seems I have two:  I'm assuming one is from before we upgraded from our old version of Enterprise (10.6.1), as it has no corresponding Data Store Item in Portal as layers were published from ArcMap; and one from afterward (post 10.7.1), which has a Data Store Item, evidently created once layers started to be published from ArcGIS Pro.  How do we know which one is the correct one to use with the script?  Does this imply that when layers are published, they are encoded with one DSID or the other?  If so, can I run the script once for each DSID, or will that have a deleterious effect?

Thank you,

GregDSIDs.png

@GregCarlino2 the mapsdb_2_ds_5dvlweynyockhtz0 data store is when you register a data store in ArcGIS Pro.  It will create an item in Portal.  If you create the data store connection in ArcGIS Server Manager by going to Site > Data Stores, it will create the data store connection as mapsdb.  

Yes, you can run the script once for each DSID, that should not have an deleterious effects.

Another approach to consider if your team or your administrators have access to Microsoft Active Directory is to create an Alias (CNAME) for the machine your SQL Server is on.  Something like this:

  • IP:  <current IP>
  • Server:  <current server name>
  • Alias (CNAME): Geodata

An event arises wherein you decide to migrate to a more recent SQL Server version.  You complete the migration, one step being to update the Active Directory entry for your Alias(CNAME):

  • IP: <new IP>
  • Server: <new server name>
  • Alias (CNAME): Geodata

The change propagates across your environment after a bit, and ta-dah, stuff works just like it did when you sorted out the whole migration process in sandbox, dev, and test environments.

tim

This is very helpful.  What if you are upgrading sql without changing the machine / instance?

Can you do a sql back up restore after upgrading sql?

I kind of failed to mention that the more recent SQL Server instance is on new hardware, didn't I?  So, yes, I would expect things to be even easier if you're upgrading in-place on your current hardware.  No AD Alias (CNAME) changes needed.

Check the SQL Server help for your version or with your DBA regarding when and how to backup when upgrading.  My experience has been good and easy with backup/restore, so nothing really to report.

tim

@AngelaVanderpas1 all you would have to do is upgrade your SQL instance.  There would be nothing on the ArcGIS side of things required.

hey guys. @JakeSkinner  Could you provide the version with that fix you mentioned? For me it is interesting that only the reference to the database is redone. Out of curiosity, in the parameters, I used the host IP address of the database for testing.

previousInstance = '10.10.x.xx' # Previous SQL Server instance
newInstance = '172.29.x.xx' # New SQL Server Instance
previousDSID = 'd96f81ca-42e6-4380-935d-a2082b910031' # Previous SQL Server Instance DSID
newDSID = 'b579344a-db7f-4e26-802f-0fa5f1eb0614' 

Can this be adapted to other database engines such as Oracle or Postgresql?

ArcGIS Pro 3.2 allows you to change the data source of the ArcGIS Server services without the need to republish.  But this can still be a hurdle if you have a large number of services. Therefore the best practice is to create a DNS alias and use that alias in the geodatabase connection files to publish the services.

Example: 

GIS-PROD-DB-1  DNS alias resolves to the database server hostname  GIS-SRV-DB-1

the geodatabases migrate from host GIS-SRV-DB-1 to GIS-SRV-DB-2

then just need to change GIS-PROD-DB-1 to resolve to GIS-SRV-DB-2

This will work fine for Oracle Easy Connect, SQL Server and PostgreSQL.

If you are using Oracle then instead of a DNS alias you can use Oracle TNSNAMES.ora that allows the same flexibility, talk to your Oracle Database Administrator to setup TNSNAMES.ora.

@MarceloMarques this is interesting, what is the workflow to change an ArcGIS Server service data source using Pro 3.2?

@JakeSkinnerthank goodness for you all good folks!

I'm in a similar situation, and so, I'm trying to prepare myself for the moment.

After reading your post, this is what I understand:

1. Backup of GIS Server (is this with WEBGISDR?)
2. Restore geodatabase to the new SQL Server instance
3. Register the geodatabase from the new SQL Server 2022 instance as a Data Store.
4. Update the 7 variables.
5. Stop GIS Server service.
6. Run the script
7. Start the GIS Service.

DONE!

Is there anything else I need to know?

Thank you,
Abi

@AbiDhakal,

That should be it.  If possible, create snapshots of your servers after the WebGISDR is restored.  If there is an error, you can easily roll back.  Also, if for some reason the script continues to fail, you can update your existing data store connections with the new .sde connection file.  The only caveat of this is that the old SQL instance will still show in Server Manager when looking at the workspace info.

@JakeSkinner 

Thank you for the message.

Why do I need to do a WebGISDR back-up of the entire ArcGIS Enterprise? I'm not trying to replace the ArcGIS Server machine. I'm only trying to replace the old 2016 SQL Server instance with a new 2022 SQL Server instance, and want to make sure that the services created from the old 2016 SQL Server instance do not have to be overwritten just because I have a new 2022 SQL Server instance.

We do take snapshots of our GIS Server everyday.

Thank you,
Abi

@AbiDhakal that was my mistake, you do not need to create a WebGISDR backup.  If you have snapshots of the server, you'll be good.

@JakeSkinner 

Thank you.

Will what you said earlier still apply in my case? "The only caveat of this is that the old SQL instance will still show in Server Manager when looking at the workspace info".

I appreciate you helping me with this.
Abi

 

@AbiDhakal that only applies if you update the existing Database Data Store connection with a new SDE connection file that is pointing to the new SQL instance.

@JakeSkinner 

I'm sorry I didn't understand that. So, if i were to have a completely new SQL Server instance with all the data inside new is when that applies?

Thank you. My apology for my ignorance.
Abi

@AbiDhakal try the steps outlined in this document first.  If for some reason it does not work, restore your snapshot.  You can then update the existing data store connection with a SDE connection file pointing to the new SQL instance:

JakeSkinner_0-1702043357088.png

Your services will redirect to this new SQL instance at this point.  However, updating the existing data store connection will not update the workspace information when you click on this option for the service.  Though, it is in fact pointing to the new SQL instance.

 
 

workspace.jpg

 

@JakeSkinner 

Ok. I think I got you.

In my case, however I do not think I will have to restore with a snapshot. I just got to find a way to connect with the new SQL Server instance.

I appreciate you. Thank you so very much.
Abi

Version history
Last update:
2 weeks ago
Updated by:
Contributors