IBM PureApplication System command-line interface and REST API
This chapter describes the PureApplication System command-line interface (pure.cli) and its REST API.
This chapter covers the following topics:
13.1 Pure.cli
The pure.cli is a Jython-like environment where you run commands to manage the PureApplication System. The environment enables a high degree of automation through scripting. Jython is a Python implementation of Java. As such, a Jython script can import both Python modules and Java classes. Pure.cli can take advantage of the vast number of the Python modules while being portable because of its ability to run on Java virtual machines (JVMs).
13.1.1 Pure.cli basics
Pure.cli imports two primary packages: admin and deployer. The admin package contains objects and functions that users can use to manage the system side of the rack. The deployer package contains resources that are associated with the workload side of the rack. In
Version 2.1, this distinction is no longer obvious on the UI. However, the underlying architecture remains the same regarding the pure.cli.
Because the pure.cli is implemented on top of Jython and resource information is stored as JSON objects, native Python data types, such as dict, list, set, and tuple, are the primary containers.
Within the PureApplication System packages, a collection of object instances, such as a collection of users, can be referenced. Example 13-1 shows a command-line interface (CLI) call to reference the global collection of admin.users.
Example 13-1 Reference an object of <class 'admin.resources.user.Users'>
>>> admin.users
Example 13-2, shows a CLI call to reference the global collection of admin.users as a Python list.
Example 13-2 Reference an object of <type 'list'>
>>> admin.users.list()
The first command returns a generic collection object (admin.resources.user.Users), and the second returns a Python list of users. The generic collection cannot use native Python list functions. For example, the slice notation works only after you call list() on the resource, as shown in Example 13-3.
Example 13-3 The collection object referenced by admin.users does not support slice notation
>>> admin.users.list()[4:5]
[{
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 17, 2014 2:35:39 PM",
"current_status": None,
"email": "[email protected]",
"groups": (nested object),
"id": "31fe4acf-9dff-4037-b050-ac8447187cdf",
"name": "Default Admin",
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/31fe4acf-9dff-4037-b050-ac8447187cdf",
"user_id": "admin"
}]
>>> admin.users[4:5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/jumper/pure.cli/lib/2.0.0.1-201506101944840/deployer/resources/resource.py", line 498, in __getitem__
raise TypeError('unsupported operand type for __getitem__: %s' % type(key))
TypeError: unsupported operand type for __getitem__: <type 'slice'>
Both of these types are equally useful. You can use the generic collection to reference an object instance by using an index or search for an object instance by using a key, as shown in Example 13-4. You can use the Python list admin.users.list() to use Python constructs such as slice and list comprehension, as shown in Example 13-4.
Example 13-4 The admin.users collection object supports reference by index and fuzzy search by ID
>>> admin.users[4]
{
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 17, 2014 2:35:39 PM",
"current_status": None,
"email": "[email protected]",
"groups": (nested object),
"id": "31fe4acf-9dff-4037-b050-ac8447187cdf",
"name": "Default Admin",
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/31fe4acf-9dff-4037-b050-ac8447187cdf",
"user_id": "admin"
}
>>> admin.users["labadmin"]
[{
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 21, 2014 7:41:27 PM",
"current_status": None,
"email": None,
"groups": (nested object),
"id": "5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"name": None,
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"user_id": "labadmin"
}]
Effectively, admin.users[“labadmin”] is identical to admin.users.list({“user_id”:”labadmin”}), as shown in Example 13-5. Each object collection has a default key that a user can use to search, thus enabling calls such as admin.users[“labadmin”]. This default key is typically the human-friendly name of the object instance as displayed on the web UI. In the case of the user object, the key is user_id.
Example 13-5 admin.users.list({‘user_id’:’labadmin’}) returns the same list as admin.users[‘labadmin’]
>>> admin.users.list({'user_id':'labadmin'})
[{
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 21, 2014 7:41:27 PM",
"current_status": None,
"email": None,
"groups": (nested object),
"id": "5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"name": None,
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"user_id": "labadmin"
}]
When you search by a key, it returns a list. The output in Example 13-5 is a list of one instance. To reference the user instance labadmin, you must use admin.users[‘labadmin’][0]. When searching for instances in this fashion, you produce a fuzzy search. If the key you passed in is not unique or is a partial match for another instance, it returns a list of those instances, as shown in Example 13-6.
Example 13-6 Fuzzy search returns all instances whose user_id field contains the search string
>>> admin.users["admin"]
[{
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 17, 2014 2:35:39 PM",
"current_status": None,
"email": "[email protected]",
"groups": (nested object),
"id": "31fe4acf-9dff-4037-b050-ac8447187cdf",
"name": "Default Admin",
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/31fe4acf-9dff-4037-b050-ac8447187cdf",
"user_id": "admin"
}, {
"auth_mode": "internal",
"bidi_national_calendar": None,
"bidi_text_direction": None,
"created_time": "Nov 21, 2014 7:41:27 PM",
"current_status": None,
"email": None,
"groups": (nested object),
"id": "5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"name": None,
"password": (write-only),
"roles": (nested object),
"shellaccounts": (nested object),
"url": "/admin/resources/users/5ba0e8d4-4150-4e76-8ae3-6d7df0907424",
"user_id": "labadmin"
}]
To list the available functions and properties of a certain collection or object, you can call help(), shown in Example 13-7.
Example 13-7 The help() function displays a brief definition, methods, and attributes available to an object
>>> help(admin.users)
A Users object represents the collection of users defined to the
system. Objects of this type are used to iterate over,
list and search for users in the system.
 
Additional help is available for the following methods:
admin, __contains__, create, delete, __delitem__, __getattr__,
__getitem__, __iter__, __len__, list, __lshift__, __repr__, __rshift__,
self, __str__, __unicode__
13.1.2 Creating and deleting objects
Classes in the admin and deployer packages implement the create() and delete() functions. To learn more about the usage of class-bound functions, call help() on that class. Example 13-8 shows the creation of a Windows Server 2012 R2 virtual image through a local OVA import. You can create instances of many types in this same fashion.
Example 13-8 The create() function can be called to create an object instance
>>> deployer.virtualimages.create('/jumper/IPAS-OVAs/Windows2012R2_maestro.ova')
{
"acl": (nested object),
"advancedoptionsaccepted": "F",
"build": "",
"created": Jul 14, 2015 1:49:25 PM,
"currentmessage": None,
"currentmessage_text": None,
"currentstatus": "RM01036",
"currentstatus_text": "Queued",
"description": "",
"hardware": None,
"id": 89,
"license": (nested object),
"licenseaccepted": "F",
"metaSignature": "30E39FA8ACDBF2FAD8C333161D704A6394FB04D0843530E25F34EFB02B6938182DF2C60B7E9BE3D9F616EFB7B6D4DF12C60D4CEA9F869E78F2E32F0E6D546593",
"name": "New virtual image 1436896165218-497",
"operatingsystemdescription": None,
"operatingsystemid": 0,
"operatingsystemversion": None,
"owner": (nested object),
"parts": (nested object),
"pmtype": "Unknown",
"productids": (nested object),
"servicelevel": "0",
"signature": "008D2E07A57C211AA4BFE9A017D3A6BDDAC0539ED6E716A77D0771242D7BF66CBF95229AEDDA6ACD3B026C60559E9B4E7C8238FF3E2BB9E80185AFDB86C14C4B",
"updated": Jul 14, 2015 1:49:25 PM,
"version": "0"
}
>>>
This create() call in Example 13-8 on page 359 returns the new virtual image instance in JSON format. You can check the status of the import with a while loop that is similar to the sample that is shown in Example 13-9. This loop checks the status of the import job every five seconds and prints to the console. The loop exits if the job fails or finishes.
Example 13-9 Sample while loop
>>> import time
>>> inst = deployer.virtualimages.list({'id':89})[0]
>>> while True:
... print ('%s Importing... | Status: %s' %(time.strftime("%c"),inst.currentstatus_text))
... if inst.currentstatus_text == 'Failed':
... print 'Import has failed. Please check the Jobs Queue for error logs.'
... break
... elif inst.currentstatus_text == 'Available':
... print 'Finished'
... break
... time.sleep(5)
...
Tue Jul 14 14:23:29 2015 Importing... | Status: Queued
Tue Jul 14 14:23:37 2015 Importing... | Status: Processing
Tue Jul 14 14:23:45 2015 Importing... | Status: Processing
Tue Jul 14 14:23:52 2015 Importing... | Status: Processing
...
You can change the sleep time (in seconds) to fit your needs. Complementary to the create() function, you can also delete the image that is created in Example 13-8 on page 359 by using the delete() function. Example 13-10 shows the expected behavior and return of this function.
Example 13-10 Use delete() to delete an object instance
>>> deployer.virtualimages.delete(89)
>>> deployer.virtualimages.list({'id':89})
[]
>>>
The delete() function takes a resource instance ID as its argument (89, in this case).
13.1.3 Listing objects in a collection
You can list all the objects in a collection, and list a subset of the collection or an instance by index. For the usage of the list() function and its expected return, see Example 13-11.
Example 13-11 Both deployer.users and deployer.users.list() return a list of all users in the system
>>> len(deployer.users)
23
>>> len(deployer.users.list())
23
>>> type(deployer.users)
<class 'deployer.resources.user.Users'>
>>> type(deployer.users.list())
<type 'list'>
>>> deployer.users
...
<abbreviated>
...
>>> deployer.users.list()
...
<abbreviated>
...
>>>
As mentioned in 13.1.1, “Pure.cli basics” on page 356, both deployer.users and deployer.users.list() return a collection of users. However, the latter returns a Python list object. You can use the Python list to use native Python list functions and constructs, such as slice and list comprehension.
13.1.4 Iterating through a collection
Iterating through a PureApplication System resource collection is simple. Standard Python flow control statements are available:
If-elif-else
For
While
Example 13-12 shows a few samples of a for statement and nesting an if statement within a for loop.
Example 13-12 Sample usage of flow control statements
>>> for inst in deployer.virtualapplications:
... print '%s | Status: %s' %(inst.deployment_name,inst.status)
...
Hadoop by Chef - Zero Config | Status: STOPPED
MJM CustomOS | Status: RUNNING
Spark on Chef - Single Slave | Status: STOPPED
UrbanCode Deploy with Patterns | Status: RUNNING
2 VMs + 1 Script - On 1 | Status: RUNNING
Hadoop Cluster - Regular Config | Status: RUNNING
2 VMs + 1 Script - On Demand | Status: RUNNING
WordPress - Medium Rare | Status: STOPPED
5 VMs + 5 Scripts | Status: RUNNING
Spark on Chef - Run List | Status: STOPPED
MJM CustomOS2 | Status: RUNNING
Node.js by Chef - Zero Config | Status: STOPPED
5 VMs + 5 Scripts - 2 scale 5 | Status: RUNNING
Red Hat Satellite Server 5.6 Pattern | Status: RUNNING
Standalone Spark on Chef - New Repo | Status: LAUNCHING
Red Hat Satellite Service | Status: RUNNING
System Monitoring | Status: RUNNING
External Chef Server | Status: RUNNING
>>> for inst in deployer.virtualapplications:
... if inst.status == 'RUNNING':
... print '%s | Status: %s' %(inst.deployment_name,inst.status)
...
MJM CustomOS | Status: RUNNING
UrbanCode Deploy with Patterns | Status: RUNNING
2 VMs + 1 Script - On 1 | Status: RUNNING
Hadoop Cluster - Regular Config | Status: RUNNING
2 VMs + 1 Script - On Demand | Status: RUNNING
5 VMs + 5 Scripts | Status: RUNNING
MJM CustomOS2 | Status: RUNNING
5 VMs + 5 Scripts - 2 scale 5 | Status: RUNNING
Red Hat Satellite Server 5.6 Pattern | Status: RUNNING
Red Hat Satellite Service | Status: RUNNING
System Monitoring | Status: RUNNING
External Chef Server | Status: RUNNING
>>>
Example 13-12 on page 361 uses a standard for the statement to iterate through all instances of the deployer.virtualapplications collection. You can also use the deployer.utils.findAll() function along with the Python lambda function, as shown in Example 13-13.
Example 13-13 deployer.utils.findAll() and the lambda function alternative to the for statement
>>> from deployer import utils
>>> print(' '.join([x.deployment_name +' | Status: '+ x.status for x in utils.findAll(lambda i: i.status == 'RUNNING', deployer.virtualapplications)]))
Base OS Instance | Status: RUNNING
MJM CustomOS | Status: RUNNING
UrbanCode Deploy with Patterns | Status: RUNNING
2 VMs + 1 Script - On 1 | Status: RUNNING
Hadoop Cluster - Regular Config | Status: RUNNING
2 VMs + 1 Script - On Demand | Status: RUNNING
5 VMs + 5 Scripts | Status: RUNNING
Script Test | Status: RUNNING
MJM CustomOS2 | Status: RUNNING
5 VMs + 5 Scripts - 2 scale 5 | Status: RUNNING
Red Hat Satellite Server 5.6 Pattern | Status: RUNNING
System Monitoring | Status: RUNNING
External Chef Server | Status: RUNNING
>>>
13.1.5 Finding an object instance by exact name
Because of fuzzy logic, when you search for an object by its key by using the list({<key>:<value>}) function, you might get more than one object instance whose substring matches your search string. To get around this issue, you may construct a method such as one that is shown in Example 13-14. The getExactObjectByName() function takes the key, the value, and the collection, and returns the instance whose key:value pair matches the provided parameters exactly.
Example 13-14 getExactObjectByName() function
def getExactObjectByName(name, arrayOfObjects, propToLookFor):
"""
Gets a specific object by name.
Parameters:
name (String): Name to match
arrayOfObjects (Array): Object to compare to.
propToLookFor (String): Property to match against (i.e. username, name)
Returns:
result (JSON Object): Object matching the exact name.
"""
match = None
# Get number of letters in the name.
nameLen = len(name)
# Loop through the array of objects.
for i in arrayOfObjects:
# Construct and evaluate the command with the specified property.
cmdToEval = 'i.' + propToLookFor
prop = eval(cmdToEval)
# If the name is a substring of the matched object, and starts at index 0, then, check its length.
# If the length is the same than the original, it is an exact match.
if prop.find(name) == 0:
if len(prop) == nameLen:
match = i
break
return match
13.1.6 Setting or updating an object attribute
You can use standard Python operators to set, update, or append values to a variable or attribute. Table 13-1shows some commonly used operators.
Table 13-1 Operators that are commonly used to update object attributes
Operator
Purpose
Example
<<
Left bitwise shift
Used to add elements to certain nested attributes
>>> admin.groups[3].roles << saved_roles
>>
Right bitwise shift
Used to subtract elements from certain nested attributes
>>> admin.groups[3].roles >> saved_roles
=
Assignment
>>> admin.groups[3].description = ‘Hello World’
+=
Append
>>> admin.groups[3].users += admin.users[1]
Example 13-15 demonstrates how the bitwise shift operators can be used to set and remove roles to and from a user group.
Example 13-15 Bitwise shift operators
>>> admin.groups[3] {
"created_time": "Jul 7, 2015 1:11:59 PM",
"description": None,
"id": "c9a2f051-d827-4a64-8e77-3e2ef93cbff5",
"member_location": "internal",
"name": "backup_users",
"roles": (nested object),
"updated_time": "Jul 7, 2015 1:12:41 PM",
"url": "/admin/resources/user_groups/c9a2f051-d827-4a64-8e77-3e2ef93cbff5",
"users": (nested object)
}
>>> ALL_ROLES = ['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER','CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'AUDIT_READONLY', 'AUDIT', 'HARDWARE_ADMIN', 'SECURITY_ADMIN', 'SECURITY_ADMIN_READONLY', 'ROLE_ADMIN', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'USER_ADMIN_READONLY', 'DR_ADMIN', 'DR_ADMIN_READONLY', 'CLOUDGROUP_MANAGER', 'HARDWARE_MANAGER']
>>> admin.groups[3].roles
['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER', 'CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'HARDWARE_ADMIN', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'USER_ADMIN_READONLY', 'CLOUDGROUP_MANAGER', 'HARDWARE_MANAGER']
>>> saved_roles = ['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER', 'CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'HARDWARE_ADMIN', 'SECURITY_ADMIN', 'SECURITY_ADMIN_READONLY', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'USER_ADMIN_READONLY', 'CLOUDGROUP_MANAGER', 'HARDWARE_MANAGER'] #saves roles for later restoring
>>> admin.groups[3].roles >> ALL_ROLES #removes all roles from the group
>>> admin.groups[3].roles
[]
>>> admin.groups[3].roles << saved_roles #re-adds saved roles
>>> admin.groups[3].roles
['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER', 'CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'HARDWARE_ADMIN', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'USER_ADMIN_READONLY', 'CLOUDGROUP_MANAGER', 'HARDWARE_MANAGER']
13.1.7 Building object collections and instances from REST JSON responses
There are some rare instances where an object attribute exists in a REST resource but not a CLI resource. If you must use these attributes to form a relationship (such as finding Virtual System Classic virtual machines (VMs) that are on certain Cloud Group; the Classic VMs and Cloud Group relationship does not exist in the CLI), you must pass in these REST resources. You can use the built-in helpers deployer.http and deployer.prettify to get direct JSONs from HTTP REST responses. From there, the JSON can be casted to whatever collection you prefer or to a Python list.
Example 13-16 demonstrates a simple function that runs an HTTP GET and saves the response to a list that is usable by the CLI. You can pass in the URI to make the function more dynamic.
Example 13-16 getThings() function turns a REST JSON response into a list that is usable by the CLI
from deployer import http, prettify
def getThings():
url ="<REST-URI>"
json = http.get(uri)
result = prettify.PrettyList(json)
return result
You can use the deployer.http helper to run the standard HTTP verbs (GET, PUT, POST, and DELETE), and the deployer.prettify object parses in and formats the output in a usable form to the CLI.
13.2 PureApplication System REST API
The PureApplication System REST API is a RESTful web service that exposes various PureApplication System components and resources. It is implemented over HTTP, so by using standard HTTP methods (GET, POST, PUT, and DELETE), users can manage these system components by using automation through the Jython-like pure.cli scripting environment or any other programming language that supports HTTP.
The PureApplication System is REST driven. Every click event on the UI translates to one or more HTTP REST requests. Understanding the REST API allows users to manage various components through automations by using any programming languages that support HTTP verbs.
13.2.1 Anatomy of a RESTful HTTP request
A RESTful HTTP request is composed of the following items:
A verb or method (GET, PUT, POST, or DELETE)
A resource URI (/resources/instances)
Request headers
Message body ({“id”:”xyz”}
In programming semantics, the four HTTP verbs are synonymous with functions or methods. The resource URI is the target object on which the method is called. The request headers are the parameters. The message body represents the instructions. Depending on the method that is called, the message body is used in different ways.
Figure 13-1 shows an HTTP GET call to an instance of the resource virtual Applications. This resource corresponds to a Virtual System instance (vSys.Next) or a Virtual Application instance on the PureApplication System web UI. The message Body tab is not available because GET methods do not have bodies.
Figure 13-1 Elements of an HTTP GET request
Take a closer look at what is being sent to the server by examining the raw HTTP request in Figure 13-2 on page 367.
GET /resources/virtualApplications/d-c0dbfea1-1019-4366-97fb-8b9cba240e74 HTTP/1.1
Host: 172.26.0.32:443
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Authorization: Basic dnR0cmFuOmwzdG0zMW5qM240
Cache-Control: no-cache
Cookie: SimpleToken=GIsFD8TNbvoKJnmfI4gqcr9BLJUy1Zj7UIsBUjJ8uW3EQmzan90Z9XKFBJxCYJJF4RVxRxHcTZ7B4we5kOAZmpjLvDtp1RcXKz7D3q/Qvb74eFXz5YOEi5viSKo7UIRowQarx1AtzNfraBA6a2D1oiKtWIGyWfY5AI++LDZXxysEt4BFQcEUv8UCYE4lxbOV; zsessionid=1436542260232b2ff272e3eb153fe534aa8cc9180ad201cefebf5c83fb9b
CSP: active
Postman-Token: c3145afc-35f3-b972-e9d3-5dae1518ba16
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.132 Safari/537.36
 
HTTP/1.1 200 OK
Cache-Control: no-cache
Cache-Control: private
Cache-Control: must-revalidate
Cache-Control: max-age=0
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: application/json
Date: Fri, 10 Jul 2015 15:41:18 GMT
Keep-Alive: timeout=70, max=500
Transfer-Encoding: chunked
Vary: User-Agent,Accept-Encoding
Figure 13-2 Basic headers of a REST HTTP request
Here is a breakdown of the essential fields of an HTTP request:
GET /resources/virtualApplications/d-c0dbfea1-1019-4366-97fb-8b9cba240e74 HTTP/1.1
 – First, the client defines the HTTP Method, Resource URI, and HTTP version.
 – The HTTP version is often auto-negotiated by the client and server.
 – You as a user should be responsible only for defining the METHOD and the Resource URI.
Host: 172.26.0.32:443
 – This is technically a request header field. However, it is derived from the resource URL, so you do not have to explicitly set it as a header.
 – The destination and protocol port of the request.
 – The user must define this as part of the Resource URL, as shown in Figure 13-1 on page 366.
 – Together with the protocol designation (port 443 for HTTPS) and the URI, this field forms the Resource URL.
Accept: */*
 – This is a request header field.
 – This field tells the server which media type is acceptable as reply.
 – This particular HTTP Request accepts all media types as reply.
Accept-Encoding: gzip, deflate, sdch
This header tells the server which encoding is acceptable.
Accept-Language: en-US,en;q=0.8
This header tells the server which language is acceptable. PureApplication System REST API supports the same languages and locales that are available on the UI and CLI.
Authorization: Basic dnR0cmFuOmwzdG0zMW5qM240
In basic user name and password authentication scheme, “dnR0cmFuOmwzdG0zMW5qM240” is a Base 64 encoding of your user name and password string that is separated by a colon (such as Username: Coffee; Password: milkandsugar; the final encoding comes from the result of BASE64ENC(Coffee:milkandsugar).
PureApplication System supports only the basic authentication method. After the server receives the first basic authentication header, it provides the client with a zsessionid header and a SimpleToken header. Any subsequent request by the client should include these two headers to avoid the need for reauthentication.
13.2.2 HTTP verbs
This section describes the four HTTP methods that the PureApplication System REST API implements.
GET
This method is called every time that your browser fetches the content of a webpage through HTTP or HTTPS. GET is considered a safe method because it is read-only. No changes are made to the resources on the server. GET calls do not have message bodies. This method can be submitted against a resource instance or a resource collection:
GET /resources/virtualApplications/
This method returns a JSON list of virtualApplication instances.
GET /resources/virtualApplications/d-c0dbfea1-1019-4366-97fb-8b9cba240e74
This method returns the JSON of the virtualApplications instance.
PUT
This method updates the resource instance’s fields with the message body, as shown in Example 13-17. It is submitted against the resource instances and not the collection:
PUT /resources/virtualApplications/d-c0dbfea1-1019-4366-97fb-8b9cba240e74
Example 13-17 The message body of the PUT request
{
“can_resume” : “true”
}
This method updates the field “can_resume” of the virtual system instance with the deployment ID above to “true.” This attribute corresponds to the Resume icon on the Virtual System Instances page on the UI.
If the resource instance does not exist, the PUT call creates an instance by using the fields in the message body. The PUT call requires a message body. Even though you can PUT an empty message body, the server’s implementation of the resource determines whether to return a status code 204 No Content or an Internal Error with status code 400 Bad Request. This is an idempotent method.
POST
This method creates a resource instance. It is submitted to the resource collection, indicating that a new instance of the same resource type should be created. A POST call generally has a message body (Example 13-18).
POST /resources/applicationPatterns/a-d3a4c9c1-d402-4740-94f6-fe1b41babd4d/virtualApplications/
Example 13-18 Message body of the POST request
{ "deployment_name": "Base OS",
"model": {
"model": {
"description": "",
"nodes": [{
"id": "OS Node",
"attributes": {
"ConfigPWD_USER.password": "<xor>Lz4sLChvLTs=",
"HWAttributes.memsize": 3072,
"HWAttributes.numvcpus": 1,
"ConfigPWD_ROOT.password": "<xor>Lz4sLChvLTs="
},
"type": "image:OS Node:IBM OS Image for AIX Systems:2.1.1.0:13",
"locked": []
},
{
"id": "Default add user",
"attributes": {
"USERNAME": "virtuser",
"PASSWORD": "<xor>PToxMzYx"
},
"type": "add-on:Default add user:1.0.0",
"locked": []
}],
"app_type": "application",
"name": "Base OS",
"patterntype": "vsys",
"links": [{
"id": "HostedOnLink_1",
"source": "Default add user",
"target": "OS Node",
"attributes": {
},
"type": "HostedOnLink"
}],
"locked": false,
"readonly": false,
"version": "1.0",
"patternversion": "1.0",
"mixinArgs": null
},
"layers": [{
"id": "layer",
"nodes": ["OS Node",
"Default add user"]
}]
},
"cloud_group": "1",
"ip_group": "2",
"environment_profile_id": "1",
"priority": "high",
"placement_only": true
}
DELETE
This method deletes the resource instance or resource collection. It can be submitted against a collection or an instance. A DELETE call does not have a message body.
DELETE /resources/virtualApplications/d-c0dbfea1-1019-4366-97fb-8b9cba240e74
13.3 CLI recipes
This section includes some example scripts that can be used as is or modified to fit your requirements. Testing and environmental differences should always be considered.
13.3.1 Importing and loading group fixes
You can use the script in Example 13-19 to load group fixes in the form of GZIP Compressed Tar Archive files (tgz). You can either hardcode the variable mypath or pass it in as an argument by using sys.argv. This path is the OS path to the directory that contains all of your pattern types. You can download the entitled pattern types as part of a release’s group fix from Fix Central. For more information about entitled fixes, see the following website:
Example 13-19 Import pattern type tgz files if the pattern type is not already installed on the rack
from os import listdir
from os.path import isfile, join
import deployer, sys, re, datetime

mypath = '/jumper/files/dd-intel4RB/ptypes/'
#onlyfiles = [ join(mypath,f) for f in listdir(mypath) if isfile(join(mypath,f)) ]
installed = set()

'''
Get list of installed ptypes
'''
for pi in deployer.plugins:
installed.add(pi.patterntype_package)
insList = list(installed)

'''
Return true if ptype exists on rack
Relies on tgz naming convention and patterntype_package name
'''
def exist(tgzname):
if re.sub('.tgz','',re.sub('-','/',tgzname)) in insList:
return True
else:
return False

toUpdate = [] # to store tgz not yet exist on rack and to be updated
toUpdatePath = [] # to store path to those tgz
for f in listdir(mypath):
if isfile(join(mypath,f)) and not exist(f):
toUpdatePath.append(join(mypath,f))
toUpdate.append(f)

cr = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
print ' '
print 'Script started %s' %(cr)
print 'ptypes to be updated: '
print '++++++++++++++++++++++++'
for thing in toUpdate:
print thing
print '++++++++++++++++++++++++'
print ' '
print 'ptypes update started...'
for pt in toUpdatePath:
k = re.compile("%s(.*)" %(mypath)).search(pt)
name = k.group(1)
try :
print "uploading %s" %(name)
t = deployer.patterntypes.create(pt)
t[0].acceptLicense()
t[0].enable()
except :
excType = sys.exc_info()[0]
excValue= sys.exc_info()[1]
if "zero.json.JsonParserException: Decoding java.io.InputStreamReader" in str(excValue):
p = re.compile("%s(.*)" %(mypath)).search(pt)
print 'ptype %s is probably already loaded (with no plugins), please check the UI' %(p.group(1))
else:
print "Caught an exception of type" + str(excType) + ":" + str(excValue)
#end try
#endFor
end = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
print 'ptypes update completes at %s' %(end)
print ' '
13.3.2 Creating users and groups from a CSV list
By using the script in Example 13-20, you can pass in a CSV file of users and create a user group, and add users to an existing user group. Here are the CSV fields:
First name
Last name
userid
email
This script is useful if there is a large list of users that must be added to one or more racks.
Example 13-20 Take a CSV list of users and add them to user-specified groups
from deployer import utils
import csv, admin

userList = []

csvFile = open('Users.csv', 'rb')


try:
spamReader = csv.reader(csvFile)
for row in spamReader:
print row
userList.append(row)
finally:
csvFile.close()
# print userList
GROUP_NAME = 'GROUP1'

ALL_ROLES = ['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER', 'CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'AUDIT_READONLY', 'AUDIT', 'HARDWARE_ADMIN', 'SECURITY_ADMIN', 'SECURITY_ADMIN_READONLY', 'ROLE_ADMIN', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'USER_ADMIN_READONLY', 'DR_ADMIN', 'DR_ADMIN_READONLY', 'CLOUDGROUP_MANAGER', 'HARDWARE_MANAGER']

GUEST_ROLES = ['CLOUD_ADMIN', 'APPLIANCE_ADMIN', 'CATALOG_CREATOR', 'PATTERN_CREATOR', 'ILMT_USER', 'CLOUD_ADMIN_READONLY', 'APPLIANCE_ADMIN_READONLY', 'PROFILE_CREATOR', 'REPORT_READONLY', 'HARDWARE_ADMIN_READONLY', 'CLOUDGROUP_ADMIN', 'CLOUDGROUP_ADMIN_READONLY', 'CLOUDGROUP_MANAGER']

def addLDGroup(GROUP_NAME):
if not utils.find(lambda r: r.name == GROUP_NAME, admin.groups[GROUP_NAME]):
u = admin.groups << {
"member_location": "ldap",
"name": GROUP_NAME
}
admin.groups[GROUP_NAME][0].roles >> ALL_ROLES
if GROUP_NAME == 'GROUP2':
admin.groups[GROUP_NAME][0].roles << GUEST_ROLES
else:
admin.groups[GROUP_NAME][0].roles << ALL_ROLES


addLDGroup(GROUP_NAME)

# group = admin.groups[GROUP_NAME][0] # only need 1 group
for user in userList:
if not utils.find(lambda r: r.user_id == user[2], admin.users[user[2]]): # verify user does not already exist
u = admin.users << { # add user to rack
"auth_mode": "ldap",
"email": user[3],
"name": user[0] + " " + user[1],
"user_id": user[2]
}
print "User %s created" % (user[0],)
13.3.3 Cleaning old deployments (non-production environments only)
The script in Example 13-21 cleans all deployments older than 30 days (excluding shared services). You can change the variable ttl to change the time to live. To exempt deployments from being automatically cleaned, users can add the “DND” string to the deployment name (this safe string can be changed). This script should be used only on non-production or test environment. The purpose of this script is to clean or purge sufficiently old deployments to recover system resources. This script can be automated as a weekly cron job.
Example 13-21 Clean an old deployment that matches a certain time to live duration
from datetime import datetime, timedelta
import time, sys

#sys.path.append("C:UsersVincentPycharmProjectsDummy2.1.0.0-201504081723317")
import deployer

ttl = timedelta(days=30)
timeout = 900 # 15 minutes
sleepInterval = 60 # 1 minute
deletionInterval = 60 # to avoid concurrency problem
allowedCheckTime = timeout / sleepInterval



def cleanApp():
for instance in deployer.virtualapplications:
if isAppPerm(instance):
print instance.deployment_name.encode(
"UTF-8") +' will not be deleted. It has the "Do Not Delete" flag or is a Shared Service.'
# elif instance.status != 'STARTED' or instance.status != 'ERROR':
# print instance.deployment_name.encode("UTF-8")+ ' might be stopping or deleting'
else:
d1 = datetime.strptime(instance.start_time,"%Y-%m-%dT%H:%M:%S.")
if datetime.now() - d1 > ttl:
try:
instance.delete()
print '###### Deleting '+instance.deployment_name+' #####'
time.sleep(deletionInterval)
except Exception, err:
print datetime.now().isoformat(
' ') + " -- " + "Error deleting " + instance.deployment_name.encode(
"UTF-8") + " manual intervention required."
print "Exception: " + str(err)
else:
print instance.deployment_name.encode(
"UTF-8") +' will not be deleted. It is not old enough.'

def cleanSys():
for instance in deployer.virtualsystems:
if isSysPerm(instance):
print instance.name.encode(
"UTF-8") +' will not be deleted. It has the "Do Not Delete" flag.'
else:
if datetime.now() - datetime.utcfromtimestamp(instance.updated) > ttl:
try:
instance.delete()
print '##### Deleting '+instance.name + ' #####'
time.sleep(deletionInterval)
except Exception, err:
print datetime.now().isoformat(
' ') + " -- " + "Error deleting " + instance.name.encode(
"UTF-8") + " manual intervention required."
print "Exception: " + str(err)
else:
print instance.name.encode(
"UTF-8") +' will not be deleted. It is not old enough.'
def isAppPerm(app):
if 'DND' in app.deployment_name or 'Tiebreaker' in app.deployment_name or app.app_type == 'service':
return True
return False


def isSysPerm(sys):
if 'DND' in sys.name or 'Tiebreaker' in sys.name:
return True
return False


def clean():
print '*****Cleaning vApp and vSys.Next (excluding Shared Services)*****'
cleanApp()

print '*****Cleaning vSys.Classic******'
cleanSys()

clean()
13.3.4 Getting full instance reports while filtering by virtual machine name
The getReport function in Example 13-22 uses the deployer.http helper to send a GET HTTP request to the server and parse the response (in this case, the instance reports) to an output file. The function takes a string argument and a CSV file argument. It then generates a VM usage report to contain all VMs whose name contains the supplied search string. This script can be complementary to the Virtual Machine Usage Report function on the PureApplication System user interface (/systemconsole/reports/machine-activity). You can use this script to tailor the usage reports with more fine-grained search parameters.
Example 13-22 Generate a VM usage report for VMs whose name contains the string “ODR”
from deployer import http, utils

def getReport(somestring, somefile):
x = admin.virtualmachines
uri = '/admin/resources/instancereport?'
iString= ''
tag= 'instid='
delim = '&'
somefile = file(somefile, 'wb')
docclose = True
for thing in x:
if somestring in thing.name:
iString+=tag+thing.id+delim
http.get(uri+iString+'csv=true', responseHandler=utils.curryFunction(utils.getResponseHandler, somefile))
if docclose:
somefile.close()

getReport('ODR', 'test.csv')
13.3.5 Running a script package
The script in Example 13-23 takes a single argument (deployment ID). It then finds eligible VMs of the specified deployment and allows the user to select one. After the VM is selected, it finds the VM’s eligible script packages (execution mode 2 and 3) and allows the user to run one. After the script is ran, a status indicator informs the user whether the script is RUNNING, FAILED, or DONE.
Example 13-23 Run a script package and output the status to stdout
from deployer import http, prettify
import time, datetime, sys

scriptName = None
myscript = None
myvm = None
mystatus = None
cmdarg = sys.argv

def echo_wait(string):
ts = datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S')
sys.stdout.write(" 33[K[%s]: %s" % (ts, string))
sys.stdout.flush()

def echo_log(string):
ts = datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S')
sys.stdout.write("[%s]: %s" % (ts, string))
sys.stdout.flush()
sys.stdout.write(" ")
sys.stdout.flush()

if len(cmdarg) != 2:
print "Usage:"
print " testSP.py deployment-ID "
sys.exit(1)


depID = str(cmdarg[1])

if depID[0] != 'd' or len(depID)!=38:
print "Invalid parameter: Deployment ID should start with the letter d"
sys.exit(1)

depl = deployer.virtualapplications.list({'id':str(depID)})

if len(depl) == 1 :
dep = depl[0]
vms = dep.virtualmachines.list()
else:
print 'The deployment ID does not exist, please verify'
sys.exit(1)

print " VM(s) in Deployment: "
#print vms
validID = []
indx = 0
for v in vms:
indx += 1
validID.append(indx)
print 'VM ID: %s | VM Name : %s' %(indx, v.name)
print " Please select the VM you'd like to run a script package on"

while True:
try:
idt = int(input("VM ID: "))
except ValueError:
print("Sorry, I didn't understand that. Please re-enter VM ID.")
continue

if idt not in validID:
print("Please enter a VM ID from the valid IDs above.")
continue
else:
#we're ready to exit the loop.
break
idt -=1
myvm = dep.virtualmachines.list()[idt]
#print myvm.name
validSP= []
print "Script package(s) in VM:"
for p in myvm.scripts:
# print p['name']
if p['execmode'] in [2,3]:
validSP.append(p['id'])
print 'Script ID: %s | Script Name: %s' %(p['id'], p['name'])
print " Please select the script package you'd like to execute"

while True:
try:
spid = int(input("Script ID: "))
except ValueError:
print("Sorry, I didn't understand that. Please re-enter Script ID.")
continue

if spid not in validSP:
print("Please enter a Script ID from the valid IDs above.")
continue
else:
#we're ready to exit the loop.
break
myscript = (i for i in myvm.scripts if i['id'] == spid).next()
#print spid
#print(myscript['name'])

d = dict([('script',myscript)])
myvm.executeScript(d)

while not mystatus:
for history in myvm.scripthistory:
if int(history.get('scriptid')) == int(myscript.get('scriptId')):
myrc = history.get('scriptstatus')[len(history.get('scriptstatus')) - 1].get('currentstatus')
if myrc in ["RM05010","RM05009","RM01012"]:
result = "Executing Script: %s [DONE]" % myscript.get('label')
echo_log("%s " % result)
sys.stdout.write(" ")
sys.stdout.flush()
mystatus = True
elif myrc in ["RM05006", "RM05007","RM05008","RM01013"]:
result = "Executing Script: %s [FAILED]" % myscript.get('label')
echo_log("%s " % result)
sys.stdout.write(" ")
sys.stdout.flush()
mystatus = True
else:
result = "Executing Script: %s [RUNNING] %s" % (myscript.get('label'), myrc)
echo_wait("%s " % result)
time.sleep(1)
echo_wait("%s ." % result)
time.sleep(1)
echo_wait("%s .." % result)
time.sleep(1)
echo_wait("%s ..." % result)
time.sleep(1)
13.3.6 Finding VMs in a particular cloud group by using a particular image
This use case can be useful to administrators who must manage and report license usage on a particular environment. You can use the script in Example 13-24 to select interactively a cloud group and a virtual image search parameter. The script can be modified to search on an environment profile instead of cloud group. You can also add additional search parameters. This script takes no argument at run time. It interactively passes in search parameters from the user and finds VMs that use a certain virtual image and is deployed in a certain cloud group.
For more information, see the IBM Knowledge Center found at the following website:
Example 13-24 Find VMs that use certain virtual image and are deployed in certain cloud group
import deployer, admin
from deployer import utils, http, prettify

mycloud = None
myimage = None
allVMs = []
allVMCs = []

'''
Store VMs from vsys.next/vapp instances to allVMs list
'''
for inst in deployer.virtualapplications:
if inst.app_type != "service":
for vinst in inst.virtualmachines:
allVMs.append(vinst)

'''
Store VMs from vsys classic instances to allVMCs list
'''
for inst in deployer.virtualsystems:
for vinst in inst.virtualmachines:
allVMCs.append(vinst)


def search(cloudid, imageid):
if cloudid == mycloud.id and imageid == myimage.id:
return True
else:
return False


def getVM():
res1 = utils.findAll(lambda vm: search(vm.environment['cloudid'], vm.virtualimage.id), allVMs)
res2 = utils.findAll(lambda vm: search(getClassicCG(vm.virtualsystem.id), vm.virtualimage.id), allVMCs)
res = res1 + res2
return res

'''
Store virtual system classic instances store their cloud group ID differently
This is the http helper mentioned in section 13.1.6
'''
def getClassicCG(vsysid):
json = http.get("/resources/instances/%s/virtualMachines" % (vsysid))
result = prettify.PrettyList(json)
return result[0]['cloudid']


print 'Usage: '
print ' findVM.py takes no arguments'
print ' Input your search parameters from prompt'
print ' This script will return a list of VMs that belongs to the Cloud Group and using a Virtual Image that you specify'
print ' '
print '========Cloud Group Selection======='
print ' '
print 'Available Cloud Groups on this system:'
print '======================================'
validCG = []
for cg in deployer.clouds:
validCG.append(cg.id)
print 'ID: %s | Name: %s | Is External: %s' % (
cg.id, cg.name, admin.clouds.list({'id': str(cg.description)})[0].is_public)
print '======================================'
print ' '
print 'Please select a Cloud Group search parameter by entering its ID:'
while True:
try:
cid = int(input("Cloud Group ID: "))
except ValueError:
print("Sorry, I didn't understand that. Please re-enter Cloud Group ID.")
continue

if cid not in validCG:
print("Please enter a Cloud Group ID from the valid IDs above.")
continue
else:
# we're ready to exit the loop.
break
mycloud = deployer.clouds.list({'id': cid})[0]
print ' '
print 'You have selected Cloud Group %s' % (mycloud.name)
print ' '
print '========Image Selection======='
print ' '
print 'Available Images on this system:'
print '======================================'
validIMG = []
for img in deployer.virtualimages:
validIMG.append(img.id)
print 'ID: %s | Name: %s | Version: %s' % (img.id, img.name, img.version)
print '======================================'
print 'Please select an Image search parameter by entering its ID:'
while True:
try:
iid = int(input("Image ID: "))
except ValueError:
print("Sorry, I didn't understand that. Please re-enter Image ID.")
continue

if iid not in validIMG:
print("Please enter a Image ID from the valid IDs above.")
continue
else:
# we're ready to exit the loop.
break
myimage = deployer.virtualimages.list({'id': iid})[0]
print ' '
print 'You have selected Image %s %s' % (myimage.name, myimage.version)

matchedVMs = getVM()
if len(matchedVMs) > 0:
print '===========Results============'
for resvm in matchedVMs:
print "VM Name: %s | Parent Deployment: %s | Deployment Status %s" % (
resvm.name, resvm.virtualsystem.deployment_name, resvm.virtualsystem.status)
print '=============================='
elif len(matchedVMs) == 0:
print 'No Match. Please run again and try different search Parameters'
else:
print 'This should not happen.'
13.3.7 Creating IP groups, adding an IP range, and attaching an IP group to a cloud group by using a script
The script in Example 13-25 takes a CSV file and parses it in as a dict. It creates an IP group. For each IP group, it adds a defined IP range and attaches the IP group to a cloud group. Here are the expected headers for the CSV file:
IP Group Name
Subnet
Mask
Gateway
VLAN ID
DNS
Secondary DNS
IPversion
Start IP
End IP
Cloud Group
You can change the key name in the script to fit your own CSV formatting.
Example 13-25 IPG2CY.py parses in IP group information from a CSV
import admin, csv, datetime, time
from deployer import utils


'''
Storing global set of rack IP
In case users need to check for existing IPs and split the range
'''
# ipset = set()
# for x in admin.ipgroups:
# for y in x.ips:
# ipset.add(y.ip)
#


# def exists(ip):
# if ip in ipset:
# return True
# else:
# return False



def createIPG(name, subnet, mask, gateway, vlan_id, dns, secondary_dns='', ip_version='ipv4'):
ipg = admin.ipgroups << {
"name": name,
"subnet": subnet,
"mask": mask,
"gateway": gateway,
"vlan_id": vlan_id,
"dns": dns,
"secondary_dns": secondary_dns,
"ip_version": ip_version
}


def populateIPG(name, from_ip, to_ip,):
ipg = utils.find(lambda i: i.name == name, admin.ipgroups)
ipg.ips.create(from_ip,to_ip)


def attach2CG(ipgname, cloud):
ipg = utils.find(lambda i: i.name == ipgname, admin.ipgroups)
cg = utils.find(lambda i: i.name == cloud, admin.clouds)
ipg.cloud = cg



f = open('ipg2.csv', 'rb')
data = csv.DictReader(f)
for l in data:
name = l['IP Group Name']
subnet = l['Subnet']
mask = l['Mask']
gateway = l['Gateway']
vlan_id = l['VLAN ID']
dns = l['DNS']
secondary_dns = l['Secondary DNS']
#ip_version
startip = l ['Start IP']
stopip = l['End IP']
cg = l['Cloud Group']
createIPG(name,subnet,mask,gateway,vlan_id,dns,secondary_dns)
print ('[%s]: IP Group %s is created') %(datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S'), name)
populateIPG(name,startip,stopip)
print ('[%s]: IP Range %s - %s is added') %(datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S'), startip, stopip)
attach2CG(name,cg)
print ('[%s]: IP Group %s is attached to Cloud Group %s') %(datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S'), name, cg)
f.close()
 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset