CA Service Management

 View Only

  • 1.  Service Desk SOAP API with python

    Posted Sep 30, 2025 12:05 PM

    Hi there!

    I've been implemented an automation of RDS management through SOAP API with python.
    Before, I tried through cURL scripts and I was successful in both read and write access.
    But with python, I got only read access, despite I got successful return in updates, the RDS not changes.

    Below, follows the cURL successful SOAP envelope and, next, the python problematic code, both to change RDS status:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://www.ca.com/UnicenterServicePlus/ServiceDesk">
       <soapenv:Header/>
       <soapenv:Body>
          <ser:updateObject>
             <sid>REDACTED</sid>
             <objectHandle>cr:8846378</objectHandle>
             <attrVals>
                <string>status</string>
                <string>ATD</string>
             </attrVals>
    <attributes>
    <string>status</string>
    </attributes>
          </ser:updateObject>
       </soapenv:Body>
    </soapenv:Envelope>

    curl -v -H "Content-Type: text/xml;charset=UTF-8" -H "SOAPAction:" -d @update_status.xml "${ENDPOINT}"

    cURL returns Ok:
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:updateObjectResponse xmlns:ns2="http://www.ca.com/UnicenterServicePlus/ServiceDesk"><updateObjectReturn>&lt;?xml version="1.0" encoding="UTF-8"?>&lt;UDSObject>&#xd;
        &lt;Handle>cr:8846378&lt;/Handle>&#xd;
        &lt;Attributes>&#xd;
            &lt;Attribute DataType="2005">&#xd;
                &lt;AttrName>status&lt;/AttrName>&#xd;
                &lt;AttrValue>ATD&lt;/AttrValue>&#xd;
            &lt;/Attribute>&#xd;
        &lt;/Attributes>&#xd;
    &lt;/UDSObject>&#xd;
    </updateObjectReturn></ns2:updateObjectResponse></soap:Body></soap:Envelope>

    def update_status_rds(pidrds=None, rdstatus=None):
        """
        Change RDS status
        Args: str (RDS UID, RDS status)
        Returns: dict (RDS handle, New RDS status, HTTP Status)

        Possible RDS statuses:
            Encaminhado = ENC
            Em Atendimento = ATD
            Em Aprovação = EMAPR
            Em Homologação = HOMOLOG
            Resolvido = RE
            Cancelado
        """
        from auth import login, logout

        if pidrds is None or rdstatus is None:
            raise ValueError("Erro: Falta RDS ou definicao de seu novo estado!")

        # Get SDM SID
        auth = login()
        sid = auth["id"]
        # Get SOAP client
        client = soapClient(SDM_ENDPOINT)
        # Creating call
        request_data = {
            "sid": sid,
            "objectHandle": pidrds,
            "attrVals": {"string": ["status", rdstatus]},
            "attributes": {"string": ["status"]},
        }
        # Execute call and receives HTTP Response object
        response = client.service.updateObject(**request_data)
        httpstatus = response.status_code
        if httpstatus == 200:
            # Extracts escaped XML
            xml_escaped = extracts_raw_soap(response.content)
            soap = xmltodict.parse(xml_escaped)

            # Extracts XML string from doSelectReturn field
            raw_xml = soap["soap:Envelope"]["soap:Body"]["ns2:updateObjectResponse"][
                "updateObjectReturn"
            ]

            # Unescapes HTML chars
            raw_xml_unescaped = html.unescape(raw_xml)

            # Parses real XML
            parsed = xmltodict.parse(raw_xml_unescaped)

            # Returns: dict (RDS handle, New RDS status, HTTP Status)
            ret = {
                "handle": parsed["UDSObject"]["Handle"],
                "updated": parsed["UDSObject"]["Attributes"]["Attribute"]["AttrValue"],
                "status": httpstatus,
            }
        else:
            raise ValueError(f"Erro ao atualizar status RDS: status HTTP {httpstatus}")

        # logout from SDM
        logout(sid)

        # Returns dict
        return ret

    Call: 
    status = update_status_rds("cr:8838867", "ATD")

    Python returns Ok:

    b'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:updateObjectResponse xmlns:ns2="http://www.ca.com/UnicenterServicePlus/ServiceDesk"><updateObjectReturn>&lt;?xml version="1.0" encoding="UTF-8"?>&lt;UDSObject>&#xd;\n    &lt;Handle>cr:8838867&lt;/Handle>&#xd;\n    &lt;Attributes>&#xd;\n        &lt;Attribute DataType="2005">&#xd;\n            &lt;AttrName>status&lt;/AttrName>&#xd;\n            &lt;AttrValue>ATD&lt;/AttrValue>&#xd;\n        &lt;/Attribute>&#xd;\n    &lt;/Attributes>&#xd;\n&lt;/UDSObject>&#xd;\n</updateObjectReturn></ns2:updateObjectResponse></soap:Body></soap:Envelope>'

    <?xml version="1.0" encoding="UTF-8"?><UDSObject>
        <Handle>cr:8838867</Handle>
        <Attributes>
            <Attribute DataType="2005">
                <AttrName>status</AttrName>
                <AttrValue>ATD</AttrValue>
            </Attribute>
        </Attributes>
    </UDSObject>

    What's wrong?





    -------------------------------------------


  • 2.  RE: Service Desk SOAP API with python

    Broadcom Employee
    Posted Sep 30, 2025 12:19 PM

    Hi Paulo,

    Are you saying that you can update the status via curl - and get the response OK, but when you try via python you can't update the status - but you still get the response OK?

    The `updateObjectReturn` seems to be similar in both calls. I'm sorry but it's hard for me to follow and understand your issue.

    Are you using the Axis based endpoint or the CXF based endpoint?

    Sebastian

    -------------------------------------------



  • 3.  RE: Service Desk SOAP API with python

    Posted Sep 30, 2025 02:59 PM

    Hi Sebastian!

    I'm using the CXF:
    https://enterprise:8443/cxf/services/USD_WebService?wsdl

    -------------------------------------------



  • 4.  RE: Service Desk SOAP API with python

    Broadcom Employee
    Posted Sep 30, 2025 03:09 PM
    Edited by Sebastian Nagy Sep 30, 2025 03:16 PM

    Hi Paulo,

    I still don't know exactly what issue you are hitting, but I went ahead and created a POC using `zeep` as SOAP client and this is what worked for me, I could see in the Classic UI that the Status of the ticket changes, and the response gave me the right status back.

    import zeep
    import xmltodict
    
    def soapClient(wsdl_url):
        """
        Create a SOAP client using zeep library
        Args: str (WSDL endpoint URL)
        Returns: zeep.Client object
        """
        try:
            client = zeep.Client(wsdl_url)
            return client
        except Exception as e:
            raise ConnectionError(f"Failed to create SOAP client: {str(e)}")
    
    
    def login_sdm(client):
        """Login to SDM service and return session ID"""
        request_data = {
            "username": "USERNAME",
            "password": "PASSWORD",
        }   
        login_response = client.service.login(**request_data)
        print(f"Login: {login_response}")    
        return login_response
    
    def logout_sdm(client, sid):
        """Logout from SDM service"""
        request_data = {"sid": sid}
        logout_response = client.service.logout(**request_data)
        print(f"Logout")
        return logout_response
    
    def update_status_rds(pidrds=None, rdstatus=None):
        """
        Change RDS status
        
        Args:
            pidrds (str): RDS UID
            rdstatus (str): New RDS status
            
        Returns:
            dict: RDS handle, updated status, and HTTP status
            
        Possible RDS statuses:
            - Encaminhado = ENC
            - Em Atendimento = ATD  
            - Em Aprovação = EMAPR
            - Em Homologação = HOMOLOG
            - Resolvido = RE
            - Cancelado
        """
        SDM_ENDPOINT = "http://XXXX:8080/cxf/services/USD_WebService?wsdl"
    
        if pidrds is None or rdstatus is None:
            raise ValueError("Error: Missing RDS ID or status definition!")
    
        # Get SOAP client and login
        client = soapClient(SDM_ENDPOINT)
        sid = login_sdm(client)
    
        # Prepare request data
        request_data = {
            "sid": sid,
            "objectHandle": pidrds,
            "attrVals": {"string": ["status", rdstatus]},
            "attributes": {"string": ["status"]},
        }
    
        print(f"Request data: {request_data}")
        
        # Execute SOAP call
        try:
            response = client.service.updateObject(**request_data)
        except Exception as e:
            print(f"Error updating RDS status: {e}")
            raise
    
        # Parse XML response to extract handle
        try:
            parsed_xml = xmltodict.parse(response)
            handle = parsed_xml['UDSObject']['Handle']
    
            # Extract AttrValue from the response
            status_attr = parsed_xml['UDSObject']['Attributes']['Attribute']
            if isinstance(status_attr, list):
                # Multiple attributes - find the status one
                for attr in status_attr:
                    if attr['AttrName'] == 'status':
                        attr_value = attr['AttrValue']
                        break
                else:
                    attr_value = None
            else:
                # Single attribute
                if status_attr['AttrName'] == 'status':
                    attr_value = status_attr['AttrValue']
                else:
                    attr_value = None
            
            print(f"Extracted handle: {handle}")
            print(f"Extracted status AttrValue: {attr_value}")
            
            result = {
                "handle": handle,
                "updated": rdstatus,
                "status": 200,
                "response": response
            }
        except Exception as e:
            print(f"Error parsing XML response: {e}")
            result = {
                "handle": None,
                "updated": rdstatus,
                "status": 500,
                "error": f"XML parsing error: {str(e)}",
                "response": response
            }
    
        print(f"Return: {result['handle']}")
        logout_sdm(client, sid)
        return result
    
    if __name__ == "__main__":
        # update_status_rds("cr:449051", "HOLD")
        update_status_rds("cr:449051", "OP")
    
    


    The console output is:


    Login: 338178029
    Request data: {'sid': 338178029, 'objectHandle': 'cr:449051', 'attrVals': {'string': ['status', 'OP']}, 'attributes': {'string': ['status']}}
    Extracted handle: cr:449051
    Extracted status AttrValue: OP
    Return: cr:449051
    Logout


  • 5.  RE: Service Desk SOAP API with python

    Posted Oct 01, 2025 10:12 AM

    Thanks a lot for your fast response, Sebastian!
    I've changed the soapClient() to ignore TLS certificates from our endpoint as follows:

    def soapClient(wsdl_url):
    	"""
    	Create a SOAP client using zeep library
    	Args: str (WSDL endpoint URL)
    	Returns: zeep.Client object
    	"""
    	try:
    		# Personalized session
    		session = Session()
    		session.headers.update(
    			{"Content-Type": "text/xml; charset=UTF-8", "SOAPAction": ""}
    		)
    		session.verify = False
    		urllib3.disable_warnings()
    		# Zeep client with raw_response
    		settings = Settings(raw_response=True)
    		client = Client(wsdl_url, transport=Transport(session=session), settings=settings)
    	except Exception as e:
    		raise ConnectionError(f"Failed to create SOAP client: {str(e)}")
    	return client

    ... but it not update that RDS at the Classic UI yet.

    I discovered that our SDM customization is very complex and, then, it could impacting in your behavior.
    I think this discussion can be closed now.

    -------------------------------------------



  • 6.  RE: Service Desk SOAP API with python

    Broadcom Employee
    Posted Oct 01, 2025 12:37 PM

    Paulo,

    It's my pleasure to try to help.

    If you get an SID, and you get a response from the server, I don't think the issue is with the WSDL client or SSL certificates. In your python call, you get:

    <?xml version="1.0" encoding="UTF-8"?><UDSObject>
        <Handle>cr:8838867</Handle>
        <Attributes>
            <Attribute DataType="2005">
                <AttrName>status</AttrName>
                <AttrValue>ATD</AttrValue>
            </Attribute>
        </Attributes>
    </UDSObject>

    This tells me that you get a response and that the status was updated, BUT ensure you update the right ticket, check the status in the DB, maybe check the activity log, maybe the jsrvr and stdlog files, 

    Maybe enable logging on CXF by adjusting `NX_ROOT\bopcfg\www\CATALINA_BASE\webapps\cxf\WEB-INF\classes\log4j2.xml` 

    Depending on what RU you have installed you might have:

    <Logger name="com.broadcom.casm.sdm.jws.handlers.SOAPMessageHandler" additivity="false" level="DEBUG">
        <AppenderRef ref="RollingFile" />
    </Logger>

    commented out, enable or add this code, then restart tomcat, and monitor jsrvr.log - to see what incoming and outgoing XML.

    If a call works with cURL, it should work from python code as well, the server doesn't really care who the client is, as long as it get's the right XML.

    I wish you success in your troubleshooting.

    Sebastian

    -------------------------------------------



  • 7.  RE: Service Desk SOAP API with python

    Posted Oct 02, 2025 09:14 AM

    Sebastian,

    I so sorry!
    The update_status_rds("cr:8838867", "ATD) was, actually, calling the wrong ticket. 
    Then the update did not occur in the expected ticket.
    I've fixed and now, everything is Ok.
    Escuse-me for the confusion and thank you very much again for the commitment

    -------------------------------------------



  • 8.  RE: Service Desk SOAP API with python

    Posted Oct 02, 2025 09:07 AM

    Hi Paulo, 

    You can use the changeStatus request to update the status of a ticket in SDM. This method doesn't work with the status name directly, but rather with its corresponding handle (for example: 'crs:5201' represents the status "Closed").
    To communicate with the webservice, you'll need the Zeep library:
    pip install zeep

    You must map the status names used in your application to their respective handles. Example:
    python
    STATUS_HANDLES = {
        "New": "crs:5200",
        "Closed": "crs:5201",
        "In Progress": "crs:5208",
        "Forwarded": "crs:5209",
        "Resolved": "crs:5205"
        # Note: Make sure to verify the correct handles in your environment
    }
    Example implementation of the changeStatus method
    from zeep import exceptions

    # Assuming this function is part of your SOAP client class
    def change_status_ticket(self, sid: str, creator: str, ticket_handle: str, new_status_handle: str, description: str):
        """
        Changes the status of a ticket using the changeStatus method.

        Args:
            sid (str): Active session ID.
            creator (str): Handle of the user performing the change (e.g., 'cnt:XXXXXX').
            ticket_handle (str): Handle of the ticket to be updated (e.g., 'cr:123456').
            new_status_handle (str): Handle of the new status (e.g., 'crs:5201').
            description (str): Comment to be added to the ticket's activity log.
        """
        request_data = {
            'sid': sid,
            'creator': creator,
            'objectHandle': ticket_handle,
            'description': description,
            'newStatusHandle': new_status_handle,
        }

        try:
            response = self.client.service.changeStatus(**request_data)
            print(f"Ticket {ticket_handle} status updated successfully!")
            return response
        except exceptions.Fault as fault:
            print(f"SOAP error while updating ticket status: {fault}")
        except Exception as e:
            print(f"Unknown error while attempting to change status: {e}")

    Example usage
    # --- Values retrieved from your application ---
    active_sid = "364033321"
    creator_handle = "cnt:6295EF08AF00DD429BBEEDC778369D0C"
    ticket_handle = "cr:8846378"
    desired_status = "Resolved"
    comment = "The issue was resolved by the network team."

    # --- Logic ---
    # 1. Get the status handle from the status name
    new_status_handle = STATUS_HANDLES.get(desired_status)

    # 2. Check if the status exists in the mapping
    if new_status_handle:
        # Assuming 'sdm_client' is an instance of your class
        # sdm_client.change_status_ticket(
        #     sid=active_sid,
        #     creator=creator_handle,
        #     ticket_handle=ticket_handle,
        #     new_status_handle=new_status_handle,
        #     description=comment
        # )
        print("API call would be made here.")
    else:
        print(f"ERROR: Status '{desired_status}' was not found in the mapping.")



    ------------------------------
    Regards,
    Felipe Nunes
    ------------------
    IT Analyst
    Prodabel
    ------------------------------



  • 9.  RE: Service Desk SOAP API with python

    Posted Oct 02, 2025 09:27 AM

    Hi Felipe!
    Thanks for your orientation!
    Yes, I use zeep into the soapClient() call, but in update_status_rds() I needed extract the raw XML to get de HTTP status code, because the SOAP doesn't give it by default.
    This has worked well with the symbol argument, like 'ATD' or 'ENC', but i'll annotate your tip.

    -------------------------------------------