ServiceNow Orchestration and OpenStack Integration

ServiceNowLogo.png

OpenStackLogo.png

This integration is a REST based web service.

I am beginning to hear more and more about OpenStack, and personally, I like their software.  It is an open source software solution for creating and managing public and private clouds.

If you want to use ServiceNow Orchestration, and leverage the OpenStack framework for cloud management, the steps below should help you get things going.  This is a very easy process with ServiceNow.  We will be making outbound REST based web service calls to the OpenStack server, using the API they have available.  There are many examples using curl and a variety of other languages to help you out as well as a very active community of OpenStack gurus.

OpenStackRest1.png

I like to set up a series of REST API definitions, using the REST Message module in ServiceNow.  Once we define these I will be able to reuse them within a ServiceNow workflow.

OpenStack requires a login token on most of their server request, so we will set up this call first.

The URL used will be the address to your OpenStack server and a post will be made.

Rest1.png

The Content-Type header value needs to be supplied as well as the content with your authentication credentials.

rest2.png

What you get back is an HTTP status code of 200 and a token in the response.  I have not shown my response here as it describes secure data elements I do not want to share on the Internet.  I did show the example from the OpenStack documentation of what the response looks like. You are interested in the token id from the “id” field.  The token can be a quite large series of characters.

rest3.png

In handling the response you will want to parse the id value form the token object.

rest4.png

As far as creating a server goes, it will require a valid tenant id.  You can make a call to the server to get this or just hard code this value as a property.  I decided to call the server.  The URL changes a bit to get a list of valid tenant ids.

rest5.png

For this call you need to include the authentication token retrieved on the previous step as a header value.

rest6.png

When you get a successful return a http status code of 200 is return along with a JSON object that includes the id of valid tenants.

rest7.png

Now we are ready to define the good stuff.  We will create a VM using the OpenStack create server method.  The URL for this is (click URL) the tenant_id is the id retrieved from the previous step.  As you can see in the screenshot below several header values need to be defined.  The project_id will be supplied when a self service user makes the request from the catalog.  They will need to select which project and which instance (or image) to apply to the this call.

The content body will dictate which flavor reference and image reference to use, as well as network specific uuid’s and security groups should be applied to the creation of the server.

rest8.png

The return will include a http code of 202 and have the newly created server specifics (including server url and admin password).

rest9.png

That is it.  At this point you will want to build out your catalog request item and a workflow that uses the REST activity to call these methods.  After this configuration is done it is pretty much drag an drop from here.  The hard part is over.  You can implement other OpenStack API calls in a similar fashion.  The more REST messages you build out the greater flexibility you will have to integrate with the various parts of OpenStack.

Cheers.

SCOM 2012 – Consuming ServiceNow Web Services with Microsoft Orchestrator

Many our our customers upgrading to SCOM 2012 may have encountered some issues using the ServiceNow 2007 R2 plugin. I have been told by SCOM experts and consultants that we should stop using the connectors our plugin installs with. The alternative approach going forward should be an outbound web service invocation from Microsoft’s Orchestration product.

I could scarcely find examples out there that showed how to do this, so here is at least one to get you going.

Before I can begin I have to give credit to Jason Petty from ServiceNow who worked on this with me and to Howard Humphrey from OneOK, who was more than generous and helpful in sharing his knowledge with ServiceNow. Thank you guys once again.

I will not go into creating and setting up SCOM. I assume you are already at that point in this blog.

PREREQUISTES:

1. You will need a SCOM server
2. You will need to install System Center 2012 Orchestrator with Runbook Designer and tools.
3. You will need to install the Operations Manager 2007 R2 Pack (found here: http://technet.microsoft.com/en-us/library/hh531770.aspx)

The Runbook Designer looks like the image below. After you install the Operations Manager 2007 R2 Pack you will have a list of activities for monitoring your SCOM alerts. We will be using some of those activities in this workflow along with the utilities provided out of the box for consuming web services.

Step 1: Create a New Runbook and drag and drop the Monitor Alert activity found in the Operations Manager 2007 R2 folder. When you double click the activity you will see a Monitor Alert Properties dialog come up. Select the SCOM server available. Select your trigger types. Create a filter. In this case any alert that contains Low Disk Space will cause this Run Book to advance to the next activity.

Step 2: Similar to ServiceNow workflow you can drop another activity into the workspace. This time we will select the Invoke Web Services activity from the Utilities folder. Connect the monitor alert to the newly created Invoke Web Services activity. In the General tab you can name this activity to whatever you want. I named mine Insert Incident. Below are the properties to set up this web service. In the details tab a WSDL is expected. You may have to include the basic authentication on the security tab if your WSDL download is protected with basic auth. Select the method you wish to use. The XML Request Payload is auto generated. I replaced the “?” with blanks. In this example I hard coded the short description, but users of MS-Runbook know they can use dynamic variables here and previous subscribed variables (in this case from the monitor alert activity).

I found this site to be the most helpful when understanding MS-Runbook and web services. http://www.systemcentercentral.com/opalis-interacting-with-web-services-part-1ndash-making-calls-and-extracting-data/.

Step 3: Once we get a response back from our incident create we need to get the incident number from the response payload. The link posted above is a great resource to show how to do this as well. There is an activity under the Utilities folder again to query XML. You can use XPATH to parse the XML looking for the response tag you expect. You can also right click in the XML Text field of the Details and select the Published Data option to get the variable associated with the response payload XML from your web service request. See image below.

Step 4: Lastly we need to update the alert that started this all. Select the connection your SCOM server runs on and select the Alert ID from the variables available (by right clicking in this field and using the slush bucket). The field we want to update on the alert is the TicketId field with the value we parsed from our previous step. You can dereference this value by right clicking and selecting it from the choice list.

Step 5: Sit back and enjoy a nice cold beverage of your choice.

This is the basic setup for creating incidents using web services out of Operations Manager. If anyone has any more information they would like to share with the community then as always I hope and encourage you to do so. Hope this helps some. If there are any issues with this – please contact my colleague Jason Petty 🙂 JK Jason. Please let me know. Cheers.

ServiceNow – Understanding tables with version control

Just a fun little useful fact I found – passing it on to the community.

Many of you have noticed that some tables allow you to track and compare the versions of the changes made. The Script Include table is a great example of what I am talking about.

If you navigate to the Script Include table, select any Script Include, and scroll to the bottom of that form you will see what I am referring to.

The secret to the sauce that makes that happen is the “update_synch=true” table attribute. Tables with this attribute have the option of personalizing their “Related Lists” and adding the “Versions” to the selected side of the slush bucket.

The “sys_update_version” table has been included to support this.

Wiki reference:
http://wiki.servicenow.com/index.php?title=Using_Update_Sets#Comparing_V…

Cheers.

ServiceNow – Dealing with XML API – Parsing the SOAP response

Not gonna really try and show off anything here – but the documentation for XML API is a bit foggy and I want to share this. I tried to use a few common XML API calls that are a bit of a challenge to find out about.

Lets pretend you get a SOAP response and you want to manually iterate through that response envelope that has this structure:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <ns0:Root xmlns:ns0="http://Schemas.SNResponse_RigData">
         <Status>Success</Status>
         <RigData>Whatever</RigData>
      </ns0:Root>
   </s:Body>
</s:Envelope>

Here are a few API calls you will need:

      this.fSoapDoc = new XMLDocument(requestXML);
      var soapBody = this.fSoapDoc.getNode("/Envelope/Body/Root");

      var nodelist = soapBody.getChildNodes();
      for (var i=0; i < nodelist.getLength(); i++) {
          var kidNode = nodelist.item(i);
          if (kidNode.getNodeType() == Packages.org.w3c.dom.Node.ELEMENT_NODE) {
            var fieldName = Packages.com.glide.util.XMLUtil.getNodeNameNS(kidNode);
            var fieldValue = Packages.com.glide.util.XMLUtil.getAllText(kidNode);
            gs.log("FIELD NAME: " + fieldName, "cmaloy");
            gs.log("FIELD VALUE: " + fieldValue, "cmaloy");
          }         
      }

This is going to get the name and value of the Status and RigData fields.

In this sample we would print to our log file:

FIELD NAME: Status
FIELD VALUE: Success
FIELD NAME: RigData
FIELD VALUE: Whatever

Of course you can do whatever at this point. If your status is Failure you can notify or update the log (etc.)

ServiceNow – How to control what comes back in the SOAP response with a View

Recently I have been getting a lot of questions and request about an old Knowledge presentation (concerning how we use views) to control a SOAP response. It may be best to blog it here and share.

First thing is first, lets create a special view on the incident table that only has number and short description and updated date. NOTE here: You want to create a view on the Incident Form if you are going to be getting an individual incident. There is a different list view when getting list. If you are getting list of incidents you would personalize the List Layout instead of the Form Layout in steps below.

Step 1: Open an Incident and Personalize Form Layout
Step 2: In the drop down section of the view select New (and give it a name).
Step 3: With your form view selected – pick the data you want in this view from the slush-bucket.
Step 4: Save (you now can see and select this view from the drop down on Incident)

At this point you can use this view in your URL get as well (using sysparam_view), but lets talk about SOAP requet.

If you use a SOAP client like SOAPUI you can put the wsdl for the incident table we are using in this example
https://instance.service-now.com/incident.do?WSDL

If you look at the get request you will see this setup:

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:inc="http://www.service-now.com/incident">
   <soapenv:Header/>
   <soapenv:Body>
      <inc:get>
         <sys_id>04b28ae2c0a8016600c8f3d86e3278f4</sys_id>
         <!--Optional:-->
         <__use_view>chris</__use_view>
      </inc:get>
   </soapenv:Body>
</soapenv:Envelope>

 

Your response will look like this (notice the response is controlled by the view):

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <SOAP-ENV:Body>
      <getResponse>
         <number>INC0000066</number>
         <short_description>Can't print legal document</short_description>
         <sys_id>04b28ae2c0a8016600c8f3d86e3278f4</sys_id>
         <sys_updated_on>2012-11-08 00:06:28</sys_updated_on>
      </getResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

 

Something important to note here is that this is data driven. You will not see the WSDL change when you create a new view, but since it is data driven you have the option of passing in the view of your choice and getting a more tailored SOAP response. Something else to note is that the sys_id is also included in your get response. You will automatically get this no matter what view you use (when calling get).

The same applies for getRecords as this test, except it will be looking for a List View (not a Form View) – so make sure you have both defined.

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:inc="http://www.service-now.com/incident">
   <soapenv:Header/>
   <soapenv:Body>
      <inc:getRecords>
         <!--Optional:-->
         <active>?</active>
         <!--Optional:-->
         <activity_due>?</activity_due>
         <!--Optional:-->
         <approval>?</approval>
         <!--Optional:-->
         <approval_history>?</approval_history>
         <!--Optional:-->
         <approval_set>?</approval_set>
         <!--Optional:-->
         <assigned_to>?</assigned_to>
         <!--Optional:-->
         <assignment_group>?</assignment_group>
         <!--Optional:-->
         <business_duration>?</business_duration>
         <!--Optional:-->
         <business_stc>?</business_stc>
         <!--Optional:-->
         <calendar_duration>?</calendar_duration>
         <!--Optional:-->
         <calendar_stc>?</calendar_stc>
         <!--Optional:-->
         <caller_id>?</caller_id>
         <!--Optional:-->
         <category>?</category>
         <!--Optional:-->
         <caused_by>?</caused_by>
         <!--Optional:-->
         <child_incidents>?</child_incidents>
         <!--Optional:-->
         <close_code>?</close_code>
         <!--Optional:-->
         <close_notes>?</close_notes>
         <!--Optional:-->
         <closed_at>?</closed_at>
         <!--Optional:-->
         <closed_by>?</closed_by>
         <!--Optional:-->
         <cmdb_ci>?</cmdb_ci>
         <!--Optional:-->
         <comments>?</comments>
         <!--Optional:-->
         <comments_and_work_notes>?</comments_and_work_notes>
         <!--Optional:-->
         <company>?</company>
         <!--Optional:-->
         <contact_type>?</contact_type>
         <!--Optional:-->
         <correlation_display>?</correlation_display>
         <!--Optional:-->
         <correlation_id>?</correlation_id>
         <!--Optional:-->
         <delivery_plan>?</delivery_plan>
         <!--Optional:-->
         <delivery_task>?</delivery_task>
         <!--Optional:-->
         <description>?</description>
         <!--Optional:-->
         <due_date>?</due_date>
         <!--Optional:-->
         <escalation>?</escalation>
         <!--Optional:-->
         <expected_start>?</expected_start>
         <!--Optional:-->
         <follow_up>?</follow_up>
         <!--Optional:-->
         <group_list>?</group_list>
         <!--Optional:-->
         <impact>?</impact>
         <!--Optional:-->
         <incident_state>?</incident_state>
         <!--Optional:-->
         <knowledge>?</knowledge>
         <!--Optional:-->
         <location>?</location>
         <!--Optional:-->
         <made_sla>?</made_sla>
         <!--Optional:-->
         <notify>?</notify>
         <!--Optional:-->
         <number>?</number>
         <!--Optional:-->
         <opened_at>?</opened_at>
         <!--Optional:-->
         <opened_by>?</opened_by>
         <!--Optional:-->
         <order>?</order>
         <!--Optional:-->
         <parent>?</parent>
         <!--Optional:-->
         <parent_incident>?</parent_incident>
         <!--Optional:-->
         <priority>?</priority>
         <!--Optional:-->
         <problem_id>?</problem_id>
         <!--Optional:-->
         <reassignment_count>?</reassignment_count>
         <!--Optional:-->
         <reopen_count>?</reopen_count>
         <!--Optional:-->
         <resolved_at>?</resolved_at>
         <!--Optional:-->
         <resolved_by>?</resolved_by>
         <!--Optional:-->
         <rfc>?</rfc>
         <!--Optional:-->
         <severity>?</severity>
         <!--Optional:-->
         <short_description>?</short_description>
         <!--Optional:-->
         <sla_due>?</sla_due>
         <!--Optional:-->
         <state>?</state>
         <!--Optional:-->
         <subcategory>?</subcategory>
         <!--Optional:-->
         <sys_class_name>?</sys_class_name>
         <!--Optional:-->
         <sys_created_by>?</sys_created_by>
         <!--Optional:-->
         <sys_created_on>?</sys_created_on>
         <!--Optional:-->
         <sys_domain>?</sys_domain>
         <!--Optional:-->
         <sys_id>?</sys_id>
         <!--Optional:-->
         <sys_mod_count>?</sys_mod_count>
         <!--Optional:-->
         <sys_updated_by>?</sys_updated_by>
         <!--Optional:-->
         <sys_updated_on>?</sys_updated_on>
         <!--Optional:-->
         <time_worked>?</time_worked>
         <!--Optional:-->
         <upon_approval>?</upon_approval>
         <!--Optional:-->
         <upon_reject>?</upon_reject>
         <!--Optional:-->
         <urgency>?</urgency>
         <!--Optional:-->
         <user_input>?</user_input>
         <!--Optional:-->
         <watch_list>?</watch_list>
         <!--Optional:-->
         <work_end>?</work_end>
         <!--Optional:-->
         <work_notes>?</work_notes>
         <!--Optional:-->
         <work_notes_list>?</work_notes_list>
         <!--Optional:-->
         <work_start>?</work_start>
         <!--Optional:-->
         <__use_view>?</__use_view>
         <!--Optional:-->
         <__encoded_query>?</__encoded_query>
         <!--Optional:-->
         <__limit>?</__limit>
         <!--Optional:-->
         <__first_row>?</__first_row>
         <!--Optional:-->
         <__last_row>?</__last_row>
         <!--Optional:-->
         <__order_by>?</__order_by>
         <!--Optional:-->
         <__order_by_desc>?</__order_by_desc>
         <!--Optional:-->
         <__exclude_columns>?</__exclude_columns>
      </inc:getRecords>
   </soapenv:Body>
</soapenv:Envelope>

 

Cheers.

ServiceNOW RBA (Orchestration) – Add an Active Directory user to an AD group – workflow activity.

If you are using ServiceNOW and are interested in creating a workflow activity that can add an Active Directory user to an Active Directory group (based on sAMAccount name) – then here is the code to do so.

Steps:

  1. Go to the workflow activity definitions and do a NEW one
  2. Give it a name and make sure the table says Global
  3. Copy the code I have posted here (after these steps) into the script section
  4. Create 3 activity variables (see script) for ad_user (samaccount name), ad_groups (comma delimited string of samaccount group name), and domain_controller (ip address to domain controller).
  5. Create 2 default conditions for success and failure (activity.result == “success” and activity.result != “success”)
  6. The activity will be available in a workflow (just drag and drop activity and use).
workflow.includeActivityDefinition("Run Powershell");

var Add_AD_Group_MembershipsActivityHandler = Class.create();
Add_AD_Group_MembershipsActivityHandler.prototype = Object.extendsObject(Run_PowershellActivityHandler, {
    initialize: function() {
        Run_PowershellActivityHandler.prototype.initialize.call(this);
    },

    //retrieves the host part of a URI. if the given value doesn't appear to be a uri, just returns what was passed in
    _getHost: function(uri) {
        var regex = new RegExp('^[a-zA-Z]*://([\\w\\.]*)[\\/\\?]?');
        return uri.replace(regex,"$1");
    },

    addEncryptedParameters: function(probe) {
        var myScriptParams={
            groupList : String(this.js(activity.vars.ad_groups)),
            userId : String(this.js(activity.vars.ad_user)),
            domainController : String(this.js(activity.vars.domain_controller))
        };
        this.addPowershellParameters(probe, myScriptParams);
    },

    _getValues: function(probe) {
	   this.host = this._getHost(this.js(activity.vars.domain_controller));
        this.cmd =
            '$pass = @()\n' +
            '$warn = @()\n' +
            '$fail = @()\n' +
            '$ers = @()\n' +
            '$out = @()\n' +
            '$ers += " "\n' +
            'function bomb ($msg) { throw New-Object System.ArgumentException($msg) }\n' +
            'function printout ($lines) { foreach ($line in $lines) { Write-Host "$line" } }\n' +
            '$rootEntry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$domainController", $cred.UserName, $cred.GetNetworkCredential().Password\n' +
            'if ($rootEntry.Properties -eq $null) { bomb("Unable to connect to AD controller $domainController as " + $cred.UserName) } \n' +
            '$search = New-Object System.DirectoryServices.DirectorySearcher $rootEntry \n' +
            '$search.Filter = "(&(objectClass=User)(objectCategory=Person)(samaccountname=$userId))" \n' +
            '$userResult = $search.FindOne() \n' +
            'if ($userResult -eq $null) { bomb($userId + " user could not be found") }\n' +
            '$user = $userResult.GetDirectoryEntry()\n' +
            '$replTextexpr1= [regex]"=" \n' +
            '$replTextexpr2= [regex]",CN" \n' +
            '$userDNb4=$user.distinguishedName.value \n' +
            '$userDNafter_1=$replTextexpr1.replace($userDNb4, "=""", 1) \n' +
            '$userDNafter=$replTextexpr2.replace($userDNafter_1, """,CN", 1) \n' +           
            '$groupList.split(",") | ForEach-Object { \n' +
            '    $groupName = $_ -replace "^\\s*|\\s*$",""\n' +
            'if ($groupName -eq "") { return } \n' +
            '    $search.Filter = "(&(objectClass=Group)(objectCategory=Group)(samaccountname=$groupName))"\n' +
            '    $groupResult = $search.FindOne()\n' +
            '    if ($groupResult -eq $null) { $fail += $groupName; $ers += "$groupName - group does not exist"; return } \n' +
            '    $groupRec = $groupResult.GetDirectoryEntry()\n' +
            '    Try { $groupRec.add("LDAP://"+$domainController+"/"+$userDNafter); $groupRec.CommitChanges(); $pass += $groupName }\n' +
            '    Catch [system.exception] {\n' +
            '        if ($_.exception.ErrorCode -eq -2147019886) { $warn += $groupName; $ers += "$groupName - $userId is already a member"; }\n' +
            '        else {$fail += $groupName; $ers += "$groupName - "+$error[0] }\n' +
            '    }\n' +
            '}\n' +
            'if ($fail -ne $null) { $list = $fail -join ","; $out += "Failed: $list" }\n' +
            'if ($pass -ne $null) { $list = $pass -join ","; $out += "Successful: $list"} \n' +
            'if ($warn -ne $null) { $list = $warn -join ","; $out += "Warnings: $list" } \n' +
            'printout($out)\n' +
            'printout($ers)\n' +
            'if ($fail -ne $null) { bomb("One or more group operations failed!") }\n';
    },

    _sensor_script: function() {
        workflow.scratchpad.ad_result = String(activity.result);
        workflow.scratchpad.ad_fault = String(activity.fault_description);
	    workflow.scratchpad.ad_output = String(activity.output);
    },

    type: 'Add_AD_Group_MembershipsActivityHandler'
});

ServiceNow JDBC Workflow Activity – Make database calls via workflow. You know you wanna.

Look, there are life rules I live by
– buy low, sell high
– learning is a life long process
– play nice with others
– if you ever find yourself the opportunity to work with John Andersen you DO IT! The guy is brilliant. Seriously, I can’t say enough about him. He was my mentor here at ServiceNow and the person who brought me on.

Anyhow – I recently had a customer that needed to make JDBC queries within a workflow, so I started working on a solution I could reuse for them and others. I mentioned my great idea to the one and only John Andersen and sure enough he was working on the same problem. I suggested instead of duplicating effort we work together. He agreed and I want to make available the combination of our efforts. I almost feel guilty saying that, because in reality his approach and code was better overall, so we went with his jdbc probe approach.

I am posting this to the community so you will try it out and help us shake out any bugs. I still have on my todo list the following items:
1. Get attachment support in the on complete handler (in case our result is an attachment).
2. Data Source support (so you can configure a data source and just select which data source you want in the workflow activity)
3. Testing with the variety of databases we offer.

Please follow this forum for any updates we make to it (hopefully we can get our development to eventually add this activity to our product down the road). Your feedback is necessary (else you can’t use our code). 🙂 Thanks all and stay tuned more to come.

Please find the latest update set here:

http://community.servicenow.com/blog/christophermaloy/jdbc-workflow-activity-make-database-calls-workflow-you-know-you-wanna