BookmarkSubscribeRSS Feed

tips & tricks for writing Lua code for the CAS server startup configuration property

Started ‎09-25-2023 by
Modified ‎09-25-2023 by
Views 496

The CAS server provides a way to run Lua program code at startup. The Lua code to run is specified with the sas.cas.instance.config: startup  property. This article will help a SAS Administrator how to develop the code for the startup property using SAS Studio and Proc LUA.

 

One common use case is to load CAS tables at CAS server start up. See Strategies for Reloading CAS Tables for other options on loading CAS tables. What we will look at is named "Strategy 2: Explicitly load tables" in the before mentioned article.

 

To show the different steps needed we use the example to load files from a caslib that have a common name pattern. We will look at the following topics:

  • Some Lua basics
  • Enable SWAT in Proc LUA, so we can use SAS Studio to develop the code.
  • How to call CAS actions and process the result within a Lua program
  • Where to add the Lua code
  • Write messages to the CAS server log and how to display them

Some Lua basics

 

From https://www.lua.org/about.html "Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description."

 

Things to be aware off:

  • Lua is a case-sensitive language.
  • No semicolon needed to end a statement.
  • It has reserved words, example and is a reserved word.
  • Names/identifiers that start with an underscore followed by uppercase letters are reserved for variables used by Lua, example _VERSION.
  • Literal strings can be delimited by matching single or double quotes.
  • To comment a line use double hyphen (--), all text that follows the double hyphen is comment till the end of line.
  • For a block comment use --[[ ... --]].
  • The unit of compilation is called a chunk.
  • The table type implements associative arrays. CAS action results are returned as variables of type table.
  • Use the print() function to write something to the log.

 

Enable SWAT in Proc LUA

 

When looking at the doc for the sas.cas.instance.config: startup property it says:

 

-- CAS session-zero startup script extensions.

-- Lua-formatted SWAT client code

-- that executes specified actions during session-zero prior to

-- clients connecting to CAS.

 

Proc LUA does not have SWAT enabled by default. So we do need some code to do this. Luckily in the documentation for Proc LUA we will find Example 13: Connecting to the CAS Server that shows us how to do this. The example relies on having an .authinfo file available. For our development purpose we do not need the complete example. The code below shows what we do need.

 

print("STARTUP Lua version:", _VERSION)
--
-- Show content of swat_enabled variable
--
print("NOTE: swat_enabled=", swat_enabled)

-- Show path and cpath info at start
print("NOTE: before")
print("NOTE: package.path=\n" .. package.path)
print("NOTE: package.cpath=\n" .. package.cpath)

-- Add required directories to path and cpath
if swat_enabled == false or swat_enabled == nil then
  print("NOTE: changing path and cpath")
  package.path = package.path .. ';/opt/sas/viya/home/SASFoundation/misc/casluaclnt/lua/?.lua'
  package.path = package.path .. ';/opt/sas/viya/home/SASFoundation/misc/casluaclnt/lua/deps/?.lua'
  package.cpath = package.cpath .. ';/opt/sas/viya/home/SASFoundation/misc/casluaclnt/lua/lib/?.so'
  swat_enabled = true

  -- Show updated path and cpath values
  print("NOTE: after")
  print("NOTE: package.path=\n" .. package.path)
  print("NOTE: package.cpath=\n" .. package.cpath)
end

-- Preload library
cas = require "swat"
print("STARTUP SWAT version", cas._VERSION)

--
-- Connect to the CAS server on your host
-- when no userid/password is provided, it is looking for an .authinfo file
--
s = cas.open("sas-cas-server-default-client",5570, "christine", "Student1")

-- Print the session ID
print("STARTUP session info ", s)

 

We now want to execute the code we have so far using Proc LUA. Proc LUA supports an INFILE= option or using SUBMIT/ENDSUBMIT blocks to execute Lua code. Using the INFILE= option it is easier to align the line info from any Lua error messages to the line in the code. For the INFILE= option to work, we do need a .lua file. We create a .lua file first (with the code from above) and then execute it using Proc LUA. As we can not directly create a .lua file in SAS Studio we use these steps:

 

  • In SAS Studio use New -> More file types -> Text
  • Add the code
  • Save the file to the file system and not the SAS Content
  • Rename the .txt file to .lua using the Explorer pane

 

From now on we can edit the .lua file in SAS Studio using the "View as text file" option from the Explorer pane. It was named enable-swat.lua.

 

Next we create a SAS program in which we use Proc LUA to execute the Lua program we just created. The fileref luapath points to the directory where we saved our .lua file from before. See Example 2: Specifying Input from an External Lua Script for more details on this. We always start with a new Lua environment to avoid anything left from previous executions.

 

/*
 * tell proc lua where to find lua code
 */
filename luapath "/home/christine/cas-startup";

/*
 * terminate an existing lua environment to ensure
 * we start with a clean environment
 */
proc lua terminate;
run;

/*
 * run the lua code to enable SWAT
 * NOTE: no file extension needed
 * the contents of the file enable-swat.lua is treated as Lua chunk
 */
proc lua infile="enable-swat";
run;

 

The SAS log should show lines like this:

STARTUP SWAT version swat v1.5.0
STARTUP session info CAS{'sas-cas-server-default-client', 5570, 'christine', session='7bf1354f-5fbf-4647-975f-824a373e32c1'}


Seeing these lines, we know we have successfully created a CAS session using SWAT in Proc LUA.

 

How to call CAS actions and process the result within a Lua program

 

We are now ready to develop the code we want to use in the sas.cas.instance.config: startup property. As mentioned earlier the code we want to develop will load all the files from a caslib that follow a given name pattern. To get the list of files to load, we will call the table.fileInfo CAS action. By default the documentation for an action will show the the syntax for how to call the action using CASL. See the top right on the syntax page where you can change between CASL, Lua, Python and R. For more details on calling CAS actions from Lua see Getting Started with Lua.

 

bm_1_cas-action-syntax-change-language-300x34.png

Select any image to see a larger version.
Mobile users: To view the images, select the "Full" version at the bottom of the page.

 

In general the syntax to call a CAS action looks like this:

 

results = s:<CAS action set name>_<CAS action name>{parm1, parm2, parmn}

 

Where results is the variable that contains the results from the CAS action. The variable s refers to the CAS session. The CAS session was create previously in the enable-swat.lua program. Next we create a .lua file as outlined above and add the following code:

 

--

-- specify the caslib you want to load

--

caslibName = "workshop"

 

--

-- get a list of all .sashdat files

--

files = s:table_fileInfo{caslib=caslibName, path="%.sashdat"}

 

By default the table.fileInfo action uses the % and _ as wildcard characters, see Using Wildcard Characters with the FileInfo and TableInfo Actions for more information. The result of the action, the variable files, is returned as a Lua table. A Lua table can be compared to a dictionary as it is known in other programming languages.

 

The table containing the files we are looking for is stored in the key files.FileInfo. To loop through all the "rows" of this table  we can use this code:

 

--

-- loop over result table, the key for the actual result is the name of the action

-- the k variable is the key (a numeric value)

-- the v variable is the value for the key, v is a table again

--

for k, v in ipairs(files.FileInfo) do

  -- have a look at the v table

  -- print("STARTUP v=", tostring(v))

  fileToLoad = v.Name -- if key (Name) is not found a nil value is returned

  -- use of format.string to replace place holders

  print(string.format("STARTUP fileToLoad=%s", fileToLoad))

end

 

Currently the code just prints the files found according to the pattern used with table.FileInfo action. To actually load the files as a CAS table we need to call the table.loadTable CAS action.

 

So within the for loop we add the call to load the table. Note that a function call in Lua can return more than one value.

 

results, status = s:table_loadTable{

  caslib=caslibName,

  casout={caslib=caslibName},

  path=fileToLoad,

  promote=true

}

print("STARTUP results=", table.tostring(results))

print("STARTUP status=", table.tostring(status))

 

Both variables, results and status are of type table. Proc LUA provides a special function to print the contents of a table. This function is helpful for debugging purposes. Please note, the function is only available while executing Lua code using Proc LUA, it is not available when executing the code as part of the startup of the CAS server.

 

Here is the complete code that also writes information about the status of the table.loadTable CAS action to the log. This is the code we will later use in the sas.cas.instance.config: startup property.

 

--
-- specify the caslib you want to load
--
caslibName = "workshop"

--
-- get a list of all sashdat files
-- 
files = s:table_fileInfo{caslib=caslibName, path="%.sashdat"}

--
-- loop over result table, the key for the actual result is the name of the action
-- the k variable is the key (a numeric value)
-- the v variable is the value for the key, v is a table again
--
for k, v in ipairs(files.FileInfo) do
  fileToLoad = v.Name  -- if key (Name) is not found a nil value is returned

  results, status = s:table_loadTable{
    caslib=caslibName,
    casout={caslib=caslibName},
    path=fileToLoad,
    promote=true
  }
 
  --
  -- write information to the cas server log
  -- the #files.FileInfo will return the number of "rows" in the table
  --
  print(string.format("STARTUP result %d of %d %s %s", k, #files.FileInfo, fileToLoad, status.messages[1]))
end

 

To execute our Lua program, named cas-startup.lua, that loads the data we add a second Proc LUA step to our SAS program like so

 

/*
 * run the startup code
 */
proc lua infile="cas-startup";
run;

 

Depending on the number of .sashdat files, messages like these should be in the SAS log:

 

STARTUP result 1 of 3 CE_PRODUCT_INFO.sashdat NOTE: Cloud Analytic Services made the file CE_PRODUCT_INFO.sashdat available as table CE_PRODUCT_INFO in caslib workshop.

STARTUP result 2 of 3 CE_ORDER_INFO.sashdat NOTE: Cloud Analytic Services made the file CE_ORDER_INFO.sashdat available as table CE_ORDER_INFO in caslib workshop.

STARTUP result 3 of 3 CE_SALES_ANALYSIS.sashdat NOTE: Cloud Analytic Services made the file CE_SALES_ANALYSIS.sashdat  available as table CE_SALES_ANALYSIS in caslib workshop.

 

I do recommend to add some prefix like STARTUP so you can find your log entries more easily. Once you run the Lua code to load the tables, they are loaded. While you develop the code the loaded CAS tables need to be deleted so that they can be loaded again.

 

Where to add the Lua code

 

As already mentioned the Lua code we developed to load files from a caslib as CAS tables needs to be added to the sas.cas.instance.config: startup property. Each CAS server has its own property. For more details on how to do this see Modifying Server Startup Configuration in SAS Viya. It has a section for the CAS server and also how to automate this. When you make changes to the startup property you need to restart the CAS server. See SAS Viya – CAS Server Life Cycle Management for more details on starting and stopping.

Write messages to the CAS server log and how to display them

In the sample code we have seen different examples on how to write something to the CAS server log using the Lua print() function. To see the log lines we have written to CAS server log we can use kubectl logs command. Another alternative is to use Four Tips for Exporting logs from OpenSearch Dashboards’ Generate CSV function, it provides access to log messages over a longer period of time.

 

We want to display all CAS server log lines containing the text "startup". As the log lines are returned as a JSON structure we make use of the jq command to just write out some of the "fields". In the example below it is the timeStamp, logger name, level and the actual log message. The -n edu in the below command defines the namespace where our CAS server is, you will need to change this according to your installation.

 

kubectl -n edu logs sas-cas-server-default-controller sas-cas-server | jq -R -r '. as $line | try (fromjson| "\(.timeStamp) \(.properties.logger) \(.level) \(.message)" ) catch $line' | grep -i startup

 

The result will look like this (partial content):

 

2023-09-04T14:27:12.180000+00:00 App.cas.config info STARTUP result 1 of 3 CE_PRODUCT_INFO.sashdat NOTE: Cloud Analytic Services made the file CE_PRODUCT_INFO.sashdat available as table CE_PRODUCT_INFO in caslib workshop.

2023-09-04T14:27:12.189000+00:00 App.cas.config info STARTUP result 2 of 3 CE_ORDER_INFO.sashdat NOTE: Cloud Analytic Services made the file CE_ORDER_INFO.sashdat available as table CE_ORDER_INFO in caslib workshop.

2023-09-04T14:27:12.203000+00:00 App.cas.config info STARTUP result 3 of 3 CE_SALES_ANALYSIS.sashdat NOTE: Cloud Analytic Services made the file CE_SALES_ANALYSIS.sashdat available as table CE_SALES_ANALYSIS in caslib workshop.

 

In case there is an error in your Lua program, you might get a message like this:

 

2023-09-04T14:09:06.791000+00:00 App error /cas/config/casstartup.lua:20: /cas/config/casstartup_usermods.lua:39: attempt to call field 'tostring' (a nil value)

 

The contents of the sas.cas.instance.config: startup property is copied to the file /cas/config/casstartup_usermods.lua this is why it is referenced in the error message. You can go to the original file with the Lua code you entered in the startup property to see what is causing the error. The error above was generated because there was still a table.tostring() function used in the code. As mentioned earlier this function is only available while executing Lua code using Proc LUA, it is not available when Lua code is executed by the CAS server.

Summary

 

We can add code to run at the startup of the CAS server. The code is written in the Lua language. Since SAS can execute a Lua program using Proc LUA, we can use SAS Studio to develop the code. The most common use case is to load some CAS tables at server start.

Version history
Last update:
‎09-25-2023 05:02 AM
Updated by:
Contributors

sas-innovate-2024.png

Don't miss out on SAS Innovate - Register now for the FREE Livestream!

Can't make it to Vegas? No problem! Watch our general sessions LIVE or on-demand starting April 17th. Hear from SAS execs, best-selling author Adam Grant, Hot Ones host Sean Evans, top tech journalist Kara Swisher, AI expert Cassie Kozyrkov, and the mind-blowing dance crew iLuminate! Plus, get access to over 20 breakout sessions.

 

Register now!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Tags