Performing Initial Configuration with the CLI
Infiniband Upgrade Procedures for Q3.2010
Associating a LUN with an FC initiator group
Associating a LUN with an FC initiator group
Scripting Aliases for Initiators and Initiator Groups
Configuring FC Client Multipathing
Configuring Solaris Initiators
Configuring Windows Initiators
Windows Tunables - Microsoft DSM Details
Configuring VMware ESX Initiators
Solaris iSCSI/iSER and MPxIO Considerations
Creating an Analytics Worksheet
Adding an iSCSI target with an auto-generated IQN
Adding an iSCSI target with a specific IQN and RADIUS authentication
Adding an iSCSI initiator which uses CHAP authentication
Adding an iSCSI initiator group
Storage Array Type Plugin (satp)
Configuration Changes in a Clustered Environment
Clustering Considerations for Storage
Clustering Considerations for Networking
Clustering Considerations for Infiniband
Preventing "Split-Brain" Conditions
A workflow is a script that is uploaded to and managed by the appliance by itself. Workflows can be parameterized and executed in a first-class fashion from either the browser interface or the command line interface. Workflows may also be optionally executed as alert actions or at a designated time. As such, workflows allow for the appliance to extended in ways that capture specific policies and procedures, and can be used (for example) to formally encode best practices for a particular organization or application.
A workflow is embodied in a valid ECMAscript file, containing a single global variable, workflow. This is an Object that must contain at least three members:
|
Here is the canonically trivial workflow:
var workflow = { name: 'Hello world', description: 'Bids a greeting to the world', execute: function () { return ('hello world!') } };
Uploading this workflow will result in a new workflow named "Hello world"; executing the workflow will result in the output "hello world!"
Workflows execute asynchronously in the appliance shell, running (by default) as the user executing the workflow. As such, workflows have at their disposal the appliance scripting facility, and may interact with the appliance just as any other instance of the appliance shell. That is, workflows may execute commands, parse output, modify state, and so on. Here is a more complicated example that uses the run function to return the current CPU utilization:
var workflow = { name: 'CPU utilization', description: 'Displays the current CPU utilization', execute: function () { run('analytics datasets select name=cpu.utilization'); cpu = run('csv 1').split('\n')[1].split(','); return ('At ' + cpu[0] + ', utilization is ' + cpu[1] + '%'); } };
Workflows that do not operate on input have limited scope; many workflows need to be parameterized to be useful. This is done by adding a parameters member to the global workflow object. The parameters member is in turn an object that is expected to have a member for each parameter. Each parameters member must have the following members:
|
The type member must be set to one of these types:
|
Based on the specified types, an appropriate input form will be generated upon execution of the workflow. For example, here is a workflow that has two parameters, the name of a business unit (to be used as a project) and the name of a share (to be used as the share name):
var workflow = { name: 'New share', description: 'Creates a new share in a business unit', parameters: { name: { label: 'Name of new share', type: 'String' }, unit: { label: 'Business unit', type: 'String' } }, execute: function (params) { run('shares select ' + params.unit); run('filesystem ' + params.name); run('commit'); return ('Created new share "' + params.name + '"'); } };
If you upload this workflow and execute it, you will be prompted with a dialog box to fill in the name of the share and the business unit. When the share has been created, a message will be generated indicating as much.
For some parameters, one does not wish to allow an arbitrary string, but wishes to rather limit input to one of a small number of alternatives. These parameters should be specified to be of type ChooseOne, and the object containing the parameter must have two additional members:
|
Using the ChooseOne parameter type, we can enhance the previous example to limit the business unit to be one of a small number of predefined values:
var workflow = { name: 'Create share', description: 'Creates a new share in a business unit', parameters: { name: { label: 'Name of new share', type: 'String' }, unit: { label: 'Business unit', type: 'ChooseOne', options: [ 'development', 'finance', 'qa', 'sales' ], optionlabels: [ 'Development', 'Finance', 'Quality Assurance', 'Sales/Administrative' ], } }, execute: function (params) { run('shares select ' + params.unit); run('filesystem ' + params.name); run('commit'); return ('Created new share "' + params.name + '"'); } };
When this workflow is executed, the unit parameter will not be entered by hand -- it will be selected from the specified list of possible options.
Some parameters may be considered optional in that the UI should not mandate that these parameters are set to any value to allow execution of the workflow. Such a parameter is denoted via the optional field of the parameters member:
|
If a parameter is optional and is unset, its member in the parameters object passed to the execute function will be set to undefined.
If, in the course of executing a workflow, an error is encountered, an exception will be thrown. If the exception is not caught by the workflow itself (or if the workflow throws an exception that is not otherwise caught), the workflow will fail, and the information regarding the exception will be displayed to the user. To properly handle errors, exceptions should be caught and processed. For example, in the previous example, an attempt to create a share in a non-existent project results in an uncaught exception. This example could be modified to catch the offending error, and create the project in the case that it doesn't exist:
var workflow = { name: 'Create share', description: 'Creates a new share in a business unit', parameters: { name: { label: 'Name of new share', type: 'String' }, unit: { label: 'Business unit', type: 'ChooseOne', options: [ 'development', 'finance', 'qa', 'sales' ], optionlabels: [ 'Development', 'Finance', 'Quality Assurance', 'Sales/Administrative' ], } }, execute: function (params) { try { run('shares select ' + params.unit); } catch (err) { if (err.code != EAKSH_ENTITY_BADSELECT) throw (err); /* * We haven't yet created a project that corresponds to * this business unit; create it now. */ run('shares project ' + params.unit); run('commit'); run('shares select ' + params.unit); } run('filesystem ' + params.name); run('commit'); return ('Created new share "' + params.name + '"'); } };
Workflows may optionally validate their input by adding a validate member that takes as a parameter an object that contains the workflow parameters as members. The validate function should return an object where each member is named with the parameter that failed validation, and each member's value is the validation failure message to be displayed to the user. To extend our example to give a crisp error if the user attempts to create an extant share:
var workflow = { name: 'Create share', description: 'Creates a new share in a business unit', parameters: { name: { label: 'Name of new share', type: 'String' }, unit: { label: 'Business unit', type: 'ChooseOne', options: [ 'development', 'finance', 'qa', 'sales' ], optionlabels: [ 'Development', 'Finance', 'Quality Assurance', 'Sales/Administrative' ], } }, validate: function (params) { try { run('shares select ' + params.unit); run('select ' + params.name); } catch (err) { if (err.code == EAKSH_ENTITY_BADSELECT) return; } return ({ name: 'share already exists' }); }, execute: function (params) { try { run('shares select ' + params.unit); } catch (err) { if (err.code != EAKSH_ENTITY_BADSELECT) throw (err); /* * We haven't yet created a project that corresponds to * this business unit; create it now. */ run('shares project ' + params.unit); set('mountpoint', '/export/' + params.unit); run('commit'); run('shares select ' + params.unit); } run('filesystem ' + params.name); run('commit'); return ('Created new share "' + params.name + '"'); } };
Workflows may emit audit records by calling the audit function. The audit function's only argument is a string that is to be placed into the audit log.
For complicated workflows that may require some time to execute, it can be useful to provide clear progress to the user executing the workflow. To allow the execution of a workflow to be reported in this way, the execute member should return an array of steps. Each array element must contain the following members:
|
As with the execute function on the workflow as a whole, the execute member of each step takes as its argument an object that contains the parameters to the workflow. As an example, here is a workflow that creates a new project, share, and audit record over three steps:
var steps = [ { step: 'Checking for associated project', execute: function (params) { try { run('shares select ' + params.unit); } catch (err) { if (err.code != EAKSH_ENTITY_BADSELECT) throw (err); /* * We haven't yet created a project that corresponds to * this business unit; create it now. */ run('shares project ' + params.unit); set('mountpoint', '/export/' + params.unit); run('commit'); run('shares select ' + params.unit); } } }, { step: 'Creating share', execute: function (params) { run('filesystem ' + params.name); run('commit'); } }, { step: 'Creating audit record', execute: function (params) { audit('created "' + params.name + '" in "' + params.unit); } } ]; var workflow = { name: 'Create share', description: 'Creates a new share in a business unit', parameters: { name: { label: 'Name of new share', type: 'String' }, unit: { label: 'Business unit', type: 'ChooseOne', options: [ 'development', 'finance', 'qa', 'sales' ], optionlabels: [ 'Development', 'Finance', 'Quality Assurance', 'Sales/Administrative' ], } }, validate: function (params) { try { run('shares select ' + params.unit); run('select ' + params.name); } catch (err) { if (err.code == EAKSH_ENTITY_BADSELECT) return; } return ({ name: 'share already exists' }); }, execute: function (params) { return (steps); } };
There are two aspects of versioning with respect to workflows: the first is the expression of the version of the appliance software that the workflow depends on, and the second is the expression of the version of the workflow itself. Versioning is expressed through two optional members to the workflow:
|
To express a minimally required version of the appliance software, add the optional required field to your workflow. The appliance is versioned in terms of the year, month and day on which the software was built, followed by a build number and then a branch number, expressed as "year.month.day.build-branch". For example "2009.04.10,12-0" would be the twelfth build of the software originally build on April 10th, 2009. To get the version of the current appliance kit software, run the "configuration version get version" CLI command, or look at the "Version" field in the system view in the BUI. Here's an example of using the required field:
var workflow = { name: 'Configure FC', description: 'Configures fibre channel target groups', required: '2009.12.25,1-0', ...
If a workflow requires a version of software that is newer than the version loaded on the appliance, the attempt to upload the workflow will fail with a message explaining the mismatch.
In addition to specifying the required version of the appliance software, workflows themselves may be versioned with the version field. This string denotes the major, minor and micro numbers of the workflow version, and allows multiple versions of the same workflow to exist on the machine. When uploading a workflow, any compatible, older versions of the same workflow are deleted. A workflow is deemed to be compatible if it has the same major number, and a workflow is considered to be older if it has a lower version number. Therefore, uploading a workflow with a version of "2.1" will remove the same workflow with version "2.0" (or version "2.0.1") but not "1.2" or "0.1".
Workflows may be optionally executed as alert actions. To allow a workflow to be eligible as an alert action, its alert action must be set to true.
When executed as alert actions, workflows assume the identity of the user that created them. For this reason, any workflow that is to be eligible as an alert action must set setid to true. Alert actions have a single object parameter that has the following members:
|
The items member of the parameters object has the following members:
|
Workflows executing as alert actions may use the audit function to generate audit log entries. It is recommended that any relevant debugging information be generated to the audit log via the audit function. For example, here is a workflow that executes failover if in the clustered state -- but audits any failure to reboot:
var workflow = { name: 'Failover', description: 'Fail the node over to its clustered peer', alert: true, setid: true, execute: function (params) { /* * To failover, we first confirm that clustering is configured * and that we are in the clustered state. We then reboot, * which will force our peer to takeover. Note that we're * being very conservative by only rebooting if in the * AKCS_CLUSTERED state: there are other states in which it * may well be valid to failback (e.g., we are in AKCS_OWNER, * and our peer is AKCS_STRIPPED), but those states may also * indicate aberrent operation, and we therefore refuse to * failback. (Even in an active/passive clustered config, a * FAILBACK should always be performed to transition the * cluster peers from OWNER/STRIPPED to CLUSTERED/CLUSTERED.) */ var uuid = params.uuid; var clustered = 'AKCS_CLUSTERED'; audit('attempting failover in response to alert ' + uuid); try { run('configuration cluster'); } catch (err) { audit('could not get clustered state; aborting'); return; } if ((state = get('state')) != clustered) { audit('state is ' + state + '; aborting'); return; } if ((state = get('peer_state')) != clustered) { audit('peer state is ' + state + '; aborting'); return; } run('cd /'); run('confirm maintenance system reboot'); } };
Here is an example workflow that creates a worksheet based on a specified drive type:
var steps = [ { step: 'Checking for existing worksheet', execute: function (params) { /* * In this step, we're going to see if the worksheet that * we're going to create already exists. If the worksheet * already exists, we blow it away if the user has indicated * that they desire this behavior. Note that we store our * derived worksheet name with the parameters, even though * it is not a parameter per se; this is explicitly allowed, * and it allows us to build state in one step that is * processed in another without requiring additional global * variables. */ params.worksheet = 'Drilling down on ' + params.type + ' disks'; try { run('analytics worksheets select name="' + params.worksheet + '"'); if (params.overwrite) { run('confirm destroy'); return; } throw ('Worksheet called "' + params.worksheet + '" already exists!'); } catch (err) { if (err.code != EAKSH_ENTITY_BADSELECT) throw (err); } } }, { step: 'Finding disks of specified type', execute: function (params) { /* * In this step, we will iterate over all chassis, and for * each chassis iterates over all disks in the chassis, * looking for disks that match the specified type. */ var chassis, name, disks; var i, j; run('cd /'); run('maintenance hardware'); chassis = list(); params.disks = []; for (i = 0; i < chassis.length; i++) { run('select ' + chassis[i]); name = get('name'); run('select disk'); disks = list(); for (j = 0; j < disks.length; j++) { run('select ' + disks[j]); if (get('use') == params.type) { params.disks.push(name + '/' + get('label')); } run('cd ..'); } run('cd ../..'); } if (params.disks.length === 0) throw ('No ' + params.type + ' disks found'); run('cd /'); } }, { step: 'Creating worksheet', execute: function (params) { /* * In this step, we're ready to actually create the worksheet * itself: we have the disks of the specified type and * we know that we can create the worksheet. Note that we * create several datasets: first, I/O bytes broken down * by disk, with each disk of the specified type highlighted * as a drilldown. Then, we create a separate dataset for * each disk of the specified type. Finally, note that we * aren't saving the datasets -- we'll let the user do that * from the created worksheet if they so desire. (It would * be straightforward to add a boolean parameter to this * workflow that allows that last behavior to be optionally * changed.) */ var disks = [], i; run('analytics worksheets'); run('create "' + params.worksheet + '"'); run('select name="' + params.worksheet + '"'); run('dataset'); run('set name=io.bytes[disk]'); for (i = 0; i < params.disks.length; i++) disks.push('"' + params.disks[i] + '"'); run('set drilldowns=' + disks.join(',')); run('commit'); for (i = 0; i < params.disks.length; i++) { run('dataset'); run('set name="io.bytes[disk=' + params.disks[i] + ']"'); run('commit'); } } } ]; var workflow = { name: 'Disk drilldown', description: 'Creates a worksheet that drills down on system, ' + 'cache, or log devices', parameters: { type: { label: 'Create a new worksheet drilling down on', type: 'ChooseOne', options: [ 'cache', 'log', 'system' ], optionlabels: [ 'Cache', 'Log', 'System' ] }, overwrite: { label: 'Overwrite the worksheet if it exists', type: 'Boolean' } }, execute: function (params) { return (steps); } };
Workflows are uploaded to the appliance by clicking on the plus icon, and they are executed by clicking on the row specifying the workflow.
Workflows are manipulated in the maintenance workflows section of the CLI.
Workflows are downloaded to the appliance via the download command, which is similar to the mechanism used for software updates:
dory:maintenance workflows> download dory:maintenance workflows download (uncommitted)> get url = (unset) user = (unset) password = (unset)
You must set the "url" property to be a valid URL for the workflow. This may be either local to your network or over the internet. The URL can be either HTTP (beginning with "http://") or FTP (beginning with "ftp://"). If user authentication is required, it may be a part of the URL (e.g. "ftp://myusername:mypasswd@myserver/export/foo"), or you may leave the username and password out of the URL and instead set the user and password properties.
dory:maintenance workflows download (uncommitted)> set url= ftp://foo/example1.akwf url = ftp://foo/example1.akwf dory:maintenance workflows download (uncommitted)> set user=bmc user = bmc dory:maintenance workflows download (uncommitted)> set password Enter password: password = ******** dory:maintenance workflows download (uncommitted)> commit Transferred 138 of 138 (100%) ... done
To list workflows, use the list command from the maintenance workflows context:
dory:maintenance workflows> list WORKFLOW NAME OWNER SETID ORIGIN workflow-000 Hello world root false <local>
To select a workflow, use the select command:
dory:maintenance workflows> select workflow-000 dory:maintenance workflow-000>
To get a workflow's properties, use the get command from within the context of the selected workflow:
dory:maintenance workflow-000> get name = Hello world description = Bids a greeting to the world owner = root origin = <local> setid = false alert = false scheduled = false
To execute a workflow, use the execute command from within the context of the selected workflow. If the workflow takes no parameters, it will simply execute:
dory:maintenance workflow-000> execute hello world!
If the workflow takes parameters, the context will become a captive context in which parameters must be specified:
dory:maintenance workflow-000> execute dory:maintenance workflow-000 execute (uncommitted)> get type = (unset) overwrite = (unset)
Any attempt to commit the execution of the workflow without first setting the requisite parameters will result in an explicit failure:
dory:maintenance workflow-000 execute (uncommitted)> commit error: cannot execute workflow without setting property "type"
To execute the workflow, set the specified parameters, and then use the commit command:
dory:maintenance workflow-000 execute (uncommitted)> set type=system type = system dory:maintenance workflow-000 execute (uncommitted)> set overwrite=true overwrite = true dory:maintenance workflow-000 execute (uncommitted)> commit
If the workflow has specified steps, those steps will be displayed via the CLI, e.g.:
dory:maintenance workflow-000 execute (uncommitted)> commit Checking for existing worksheet ... done Finding disks of specified type ... done Creating worksheet ... done