I recently wrote the following script as part of some tools for a phased migration of users to a new V11 cluster. We were routing pstn calls through the new cluster so could not place extensions of users not yet migrated into live partitions until their phones were cutover and registered on the new platform.
I therefore placed all extensions into a "Holding" partition not a member of a CSS which enabled us to verify the user and device configuration without impacting the users, however this also created a following problems on cutover.
1) New Cluster - Requirement to move the migrated users extensions from holding to live
2) Old Cluster - Requirement to move the migrated users extensions from live to holding
When migrating hundreds of users, changing the partition indivudually across two clusters is a lot of work, so it was imperitive to try and automate the process - enter Python!
I previously obtained the pkid's of the relevent partitions with sql querys similar to the following:
1: select pkid from routepartition where name = "PAR_HOLDING"
We build the xml message with the following sql query:
1: select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'
1: msg = """
2: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">
3: <soapenv:Header/>
4: <soapenv:Body>
5: <ns:executeSQLQuery sequence="?">
6: <sql>select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'
7: </sql>
8: </ns:executeSQLQuery>
9: </soapenv:Body>
10: </soapenv:Envelope>"""
Post to the api
1: # Create the Requests Connection
2: post = requests.post(url11, data=msg, headers=headers11query, verify=False, auth=('user', 'password'))
We then parse and iterate over the returned xml string to obtain all dnorpattern instances by looking for xml tags which match "dnorpattern" and returning their text value. In this case the number length of the two clusters are different i.e. E164 on the new and four digits on the old. We create two variables, one "newdn" which holds the full E164 value and "olddn" which takes the last four digits, before passing the variables to the relevent functions which actually do the work of changing the partition.
1: # Parse the response string
2: response = ElementTree.fromstring(post.content)
3:
4: # Find phone element in root
5: result = response.iterfind(".//row/*")
6:
7: for r in result:
8: time.sleep(1.5)
9: if r.tag == 'dnorpattern':
10: newdn = r.text
11: # Take e164 number and take last 4 digits
12: olddn = r.text[-4:]
13: # Pass e164 number to cluster11 function
14: cluster11(newdn)
15: # Pass 4 digit number to cluster86 function
16: cluster86(olddn)
Full Code :
1: __author__ = 'Mitch.Dawson'
2: import requests
3: from xml.etree import ElementTree
4: import time
5:
6: # CUCM URL's
7: url11 = 'https://new:8443/axl/'
8: url86 = 'https://old:8443/axl/'
9:
10: # V11 CUCM Headers
11: headers11query = {'Content-Type': 'text/xml',
12: 'SOAPAction': 'CUCM:DB ver=11.0 executeSQLQuery'}
13:
14: headers11update = {'Content-Type': 'text/xml',
15: 'SOAPAction': 'CUCM:DB ver=11.0 executeSQLUpdate'}
16:
17: # V8.6 CUCM Headers
18: headers86update = {'Content-Type': 'text/xml',
19: 'SOAPAction': 'CUCM:DB ver=8.5 executeSQLUpdate'}
20:
21:
22: def findextensions():
23:
24: """
25: Find numbers on cucm 11 cluster in PAR_HOLDING
26: :return:
27: """
28:
29: msg = """
30: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">
31: <soapenv:Header/>
32: <soapenv:Body>
33: <ns:executeSQLQuery sequence="?">
34: <sql>select dnorpattern from numplan where fkroutepartition ='72f20467-ddf1-52fb-5586-682a9ef3ea91'
35: </sql>
36: </ns:executeSQLQuery>
37: </soapenv:Body>
38: </soapenv:Envelope>"""
39:
40: # Create the Requests Connection
41: post = requests.post(url11, data=msg, headers=headers11query, verify=False, auth=('user', 'password'))
42:
43: # Parse the response string
44: response = ElementTree.fromstring(post.content)
45:
46: # Find phone element in root
47: result = response.iterfind(".//row/*")
48:
49: for r in result:
50: time.sleep(1.5)
51: if r.tag == 'dnorpattern':
52: newdn = r.text
53: # Take e164 number and take last 4 digits
54: olddn = r.text[-4:]
55: # Pass e164 number to cluster11 function
56: cluster11(newdn)
57: # Pass 4 digit number to cluster86 function
58: cluster86(olddn)
59:
60:
61: def cluster11(dn):
62:
63: """
64: Move patterns to PAR_RESOURCES
65: :param dn:
66: :return:
67: """
68:
69: # Message to Post
70: msg = """
71: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.0">
72: <soapenv:Header/>
73: <soapenv:Body>
74: <ns:executeSQLUpdate sequence="?">
75: <sql>update numplan set fkroutepartition = 'bfc7a079-83a5-10cc-a944-74fb1f17064b'
76: where dnorpattern = "{0}"
77: </sql>
78: </ns:executeSQLUpdate>
79: </soapenv:Body>
80: </soapenv:Envelope>""".format(dn)
81:
82: # Create the Requests Connection
83: post = requests.post(url11, data=msg, headers=headers11update, verify=False, auth=('user', 'password'))
84:
85: # Parse the response string
86: response = ElementTree.fromstring(post.content)
87:
88: # Find phone element in root
89: result = response.iterfind(".//return/*")
90: for i in result:
91: if i.tag == 'rowsUpdated':
92: if i.text == '1':
93: print('#### Successfully moved ' + str(dn) + ' to PAR_RESOURCES ####')
94: else:
95: print('#### The response indicates no rows were updated for dn ' + str(dn) + ' ####')
96:
97:
98: def cluster86(dn):
99: """
100: Move numbers to staging partition on 8.6 cluster
101: :param dn:
102: :return:
103: """
104:
105: # Message to Post
106: msg = """
107: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/8.5">
108: <soapenv:Header/>
109: <soapenv:Body>
110: <ns:executeSQLUpdate sequence="?">
111: <sql>update numplan set fkroutepartition = '61de9659-bc10-fca0-aa5e-81dc4b7e2fe4'
112: where dnorpattern = "{0}"
113: </sql>
114: </ns:executeSQLUpdate>
115: </soapenv:Body>
116: </soapenv:Envelope>""".format(dn)
117:
118: # Create the Requests Connection
119: post = requests.post(url86, data=msg, headers=headers86update, verify=False, auth=('user', 'password'))
120:
121: # Parse the response string
122: response = ElementTree.fromstring(post.content)
123:
124: # Find phone element in root
125: result = response.iterfind(".//return/*")
126: for i in result:
127: if i.tag == 'rowsUpdated':
128: if i.text == '1':
129: print('#### Successfully moved ' + str(dn) + ' to staging partition ####')
130: else:
131: print('#### The response indicates no rows were updated for dn ' + str(dn) + ' ####')
132:
133:
134: findextensions()