next up previous contents
Next: 7. Design Analysis Up: Status Server Detailed Design Previous: 5. Client C API   Contents

Subsections

6. Software Design

This section provides the design details of the Status Server. At this point, the document only covers the data structures and software components which make up the Status Server. Coverage of the Client API will be added to this document soon.

6.1 Status Server Data Structures

There are several key data structures used to hold Status Server data. Figure 13 illustrates the data structures used by the Status Server. The lines within the figure illustrate how the data is structured and the relationships between data structures. Each data structure is explained in more detail in the following sections.

Figure 13: Status Server Data Structures
\begin{figure}
\center
\epsfig {file=ss_data_structure.eps}\end{figure}

6.1.1 Client Connection Data (client_info_t)

The sockio library will manage the low-level socket details associated with the client-server socket connection. At a higher level, this data structure will be used to manage Status Server information associated with each client connection.


     typedef struct {
        char *prg_name;
        pid_t pid;
        unsigned_char ip_address[4];
        char *hostname;
        time_t login_ts;
        char *current_path;
        BOOLEAN is_mbox_empty;
        BOOLEAN is_client_notified;
        BOOLEAN is_monitor_in_prog;
        mon_info_t *monitor_ptr;
        BOOLEAN is_ls_in_prog;
        mon_info_t *ls_ptr;
        linked_list *touch_list;
        linked_list *ls_list;
        linked_list *monitor_list;
        BOOLEAN is_protocol_error;
     } client_info_t;

More details regarding each of the fields in the client_info_t structure can be found in figure 14.

Figure 14: Description of Fields within Client Data Structure
\begin{figure}
\begin{tabular}{\vert l\vert l\vert p{10cm}\vert}
\hline
\textbf...
.... FALSE if a protocol error has not occured.\\
\hline
\end{tabular}\end{figure}


6.1.2 Directory and Object Data (node_info_t)

This section outlines the data structure used to define the Status Server directory hierarchy and store object data. Each node within the Status Server hierarchical structure is described by a node_info_t structure. A single data structure is used to hold either object data or directory data since both node types have very similar data requirements. The data stored within the node_info_t data structure will be object data if the node_list is set to NULL. If the node_list field is not set to NULL, the data stored in the node_info_t data structure will be directory data. When the node_list is not null, it is possible for a directory to have both subdirectories and/or objects associated with the directory. The lifetime and monitor_list fields within the data structure are data object specific and will not apply to directory data. If it is subsequently determined that placing a monitor on a directory is useful, the value and monitor_lists could be used for this purpose.

The value field within the node_info_t structure will contain valid data or an indication of why the value is not valid. Valid values will always be enclosed within double quotes. If the value of an object is not valid, it will not be enclosed within double quotes. For example, the following values would be considered valid: ``data'', ``0.0'', or ``sample data''. If a value is not enclosed within double quotes, it must always be one of the following values: NONEXISTENT, UNDEFINED, or EXPIRED in the case of a data object. Directories will always contain the string ``DIRECTORY'' in the value field.

It is possible for a data object to have NONEXISTENT populated in the value field. While it may seem strange to define a data object with a state indicating that it doesn't exist, this is used to support the ability to apply a monitor on a data object which has not been created. In this case, the required directories to hold the data object will be created and the data object will be created with a NONEXISTENT value. As a result, it will become possible to define pointers between a monitor and the object being monitored. If a subsequent ``touch'' request is made to a data object, the value of the data object will no longer indicate NONEXISTENT.

In addition to storing the state of a directory or data object in the value field, the value_state field contains an enumerated type indicating the state of an object. This field is added to enable more efficient state comparisons.

When the Status Server is initialized, it will contain only one directory node, which is that of the root directory. Much like the UNIX file system, the root directory is described by a single forward slash '/'. Additional directories will be created when the Status Server receives client ``touch'', ``touchdir'', or ``monitor'' requests.

Data object nodes are created when a client either initiates a ``touch'' or ``monitor'' request. As previously mentioned, a data object will use the value, lifetime, and monitor_list fields within the node structure.

In the case of either a directory or object data, a node will contain a pointer to its parent. In the case of a directory, this will always be the parent directory. The root node of the directory is a special case and will have the parent pointer set to itself. Data objects will always point to the directory which has the linked list containing a pointer to the data object itself.


     typedef struct {
        char *name;
        char *comment;
        char value[MAX_VALUE_SIZE];
        node_state_t value_state;
        char *full_name;
        node_info_t *parent;
        time_t creation_ts;
        time_t update_ts;
        time_t lifetime;
        linked_list *touch_list;
        linked_list *node_list;
        linked_list *monitor_list;        
        linked_list *ls_list;
     } node_info_t;

    
     typedef enum {
        SS_NONEXISTENT,
        SS_NOTDEFINED,
        SS_EXPIRED,
        SS_VALID
     } node_state_t;

More details regarding each of the fields in the node_info_t structure can be found in figure 15.

Figure 15: Description of Fields within the Directory and Data Object Structure
\begin{figure}
\begin{tabular}{\vert l\vert p{4cm}\vert p{10cm}\vert}
\hline
\t...
...eturn directory and data object information.\\
\hline
\end{tabular}\end{figure}

6.1.3 Monitoring Data (mon_info_t)

This section outlines the data structure used to store monitoring information for a client based on modifications to an object. The information within the monitoring data structure is essentially a bridge between a client and data object. Whenever a client requests a monitor to be placed on an object, the object will be created if it didn't already exist, before the monitor object is created. Once the monitor object is created, a pointer to the object will be stored within the monitor lists of both the client object and data object being monitored. If a client disconnects from the Status Server, the monitoring objects and monitor object reference will be removed before the client object is removed.


     typedef struct {
        node_info_t *object;
        client_info_t *client;
        char prev_sent_value[MAX_VALUE_SIZE];
        time_t prev_sent_ts;
        double deadband;
        time_t creation_ts;
     } mon_info_t;

More details regarding each of the fields in the mon_info_t structure can be found in figure 16.

Figure 16: Description of Fields within Monitoring Data Structure
\begin{figure}
\begin{tabular}{\vert l\vert l\vert p{10cm}\vert}
\hline
\textbf...
... since 00:00:00 hours, GMT, January 1, 1970.\\
\hline
\end{tabular}\end{figure}

6.2 Status Server Software Components

The Status Server is broken in a set of components illustrated in figure 17. A brief description of each of the components follows with a more in depth description available in the following sections.

Figure 17: Status Server Software Components
\begin{figure}
\center
\epsfig {file=ss_components.eps}\end{figure}

6.2.1 Message Handling Services

This component interacts closely with libsockio, and will set up the following callback routines to be called by libsockio.

This is the main component of the Status Server and it initiates all the message processing done by the Status Server. In addition, it contains main() and sets up the sockio library calls to initiate and manage the server socket connection. The message handler also handles a reload of the Status Server from serialized data. The Status Server will save it's internal state via a set of messages similar to the messages sent by a client. As a result, restoring the Status Server is simply a process of replaying a set of previously sent transactions.

The main() function within the message handler will set up the commands which manage the interaction with the sockio library. An example of a code segment which could be used to manage the socket communications via the sockio library is as follows:


   int main(int argc, const char *argv[])
   {
      sockserv_t *statserv = sockserv_create(port #);

      /* Check if the socket could be created successfully */
      if (!statserv)
         exit(EXIT_FAILURE);

      /* Set up the callback functions */
      statserv->client_recv_hook = client_recv_hook;
      statserv->client_del_hook = client_del_hook;
      statserv->client_add_hook = client_add_hook;
      statserv->client_send_hook = client_send_hook;

      /* Set up an infinite loop to manage the socket communication */
      for (;;) {

         /* Service all time dependent objects */
         serviceTimeDepObjects(void);

         /* Continue calling sockserv_run with a timeout  */
         /* value of zero if it returns a positive return */
         /* value.  This indicates that something was     */
         /* received or sent during the last iteration.   */
         while (sockserv_run(statserv, 0) > 0) { ; }

         /* Call sockserv_run with a timeout interval     */
         /* corresponding to when the next time dependent */
         /* object should expire.                         */
         sockserv_run(statserv, getMinimumTimoutInterval() * 100);
      }
      exit(0);
   }

The message handler will handle each Client request and is responsible for writing a response to sockio buffer provided by the client_recv_hook() and client_send_hook() callback functions.

When a request is received from a client, it will be checked to make sure it is a valid URL formatted string. If so, the first argument will parsed and checked against the set of known commands. If it is recognized as a legal command, it will be processed according to the type of command it is. Each of the following sections provides a brief overview of the processing performed for each command and the utility functions within the other software components which will be called. Most of the functionality has been described as the pseudocode processing which occurs when each command is received by the Status Server.

Register Client with the Status Server

The pseudocode processing for how a client ``register'' request is handled is as follows:


     if (register message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     update the client object with updateClient()
     write a positive response to the sockio buffer

Disconnect from the Status Server

The pseudocode processing for how a client ``logoff'' request is handled is as follows:


     call the client\_del\_hook callback function
     write an empty string (buf[0] = '\0') to the sockio buffer

Create an object or register the intent to modify an object

The pseudocode processing for how a client ``touch'' request is handled is as follows:


     if (touch message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to create/retrieve the object from the hierarchy.   */
     /* The create parameter to getObject() must be set to TRUE */
     /* and the create_valid parameter must be set to TRUE.     */
     if (getObject() == NULL) {
        Status Server internal error
        write a negative response to the sockio buffer
        return
     }
     else {
        if a lifetime was specified, set it with setObjectLifetime()
        if a comment was specified, set it with setNodeComment()
     }
     add the data object to the client touch list using addTouchNode()
     write a positive response to the sockio buffer

Update an object

The pseudocode processing for how a client ``put'' request is handled is as follows:


     if (put message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to retrieve the object from the hierarchy.  Both  */
     /* The create parameter and create_valid parameters must */
     /* be set to false in the call to getObject().           */
     if (getObject() == NULL) {
        write object does not exist error response to the sockio buffer
        return
     }

     /* Check if the object was previously created, but has a */
     /* value of NONEXISTENT */
     if (isNodeNonexistent() == TRUE) {
        write object does not exist error response to the sockio buffer
        return
     }

     /* Check to see if this object is within the list of */
     /* objects the client has performed a ``touch'' on   */
     if (touchNodePerformed() == FALSE) {
        write permission denied error response to the sockio buffer
        return
     }

     Update the value of the object with setObjectValue()
     write a positive response to the sockio buffer

The pseudocode processing is slightly different than the flow diagram for an update in the current Functional Specification document. In this design, the Status Server will always traverse the directory and data object hierarchy in an attempt to find the data object and from there determine if the client has performed a touch. This is a simpler and more efficient implementation than checking the touch_list associated with the client object first to see if the object has been touched by the client. This would not be the case if all client requests were expressed in terms of an absolute path. However, by traversing the directory and data object hierarchy it is possible to resolve the path of the object on the fly instead of converting the path string to absolute path, finding a string match on the absolute path name in the client object touch_list and then referencing the data object. As a result, the first response that a client may see is that the data object does not exist. Only if the object exists is it possible for the client to receive a notification that it has not performed a touch on the data object.

Retrieve an object

The pseudocode processing for how a client ``get'' request is handled is as follows:


     if (get message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to retrieve the object from the hierarchy.  Both  */
     /* The create parameter and create_valid parameters must */
     /* be set to false in the call to getObject().           */
     if (getObject() == NULL) {
        write object does not exist error response to the sockio buffer
     }
     else {
        write positive data object value response to the sockio buffer
     }

Initiate a monitor on an object

The pseudocode processing for how a client monitoring request is handled is as follows:


     if (monitor message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to create/retrieve the object from the hierarchy.   */
     /* The create parameter to getObject() must be set to TRUE */
     /* and the create_valid parameter must be set to FALSE.    */
     if (getObject() == NULL) {
        Status Server internal error
        write a negative response to the sockio buffer
        return
     }

     /* Check to see if a monitor already exists on that */
     /* object for the requesting client */
     if (getMonitorByClient() == NULL) {

        /* Check to make sure the monitoring object could */
        /* be successfully created.                       */
        if (createMonitor() == PASS) {
           write a positive response to the sockio buffer.
           return
        }
        else {
           Status Server internal error
           write a negative response to the sockio buffer.
           return
        }
     }
     else {
        Update the deadband for a monitor with updateMonitorDeadband()
     }
     write a positive response to the sockio buffer.

Remove a monitor from an object

The pseudocode processing for how a monitor will be removed from an object:


     if (unmonitor message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to retrieve the object from the hierarchy.  Both  */
     /* the create parameter and create_valid parameters must */
     /* be set to false in the call to getObject().  If the   */
     /* object does not exist, the monitor also won't exist   */
     /* since an object can not be removed while it is being  */
     /* monitored.  */
     if (getObject() == NULL) {
        write a monitor does not exist error response to the sockio buffer.
        return
     }

     /* Check to see if a monitor already exists on that */
     /* object for the requesting client */
     if (getMonitorByClient() == NULL) {
        write a monitor does not exist error response to the sockio buffer.
        return
     }

     /* Remove the monitoring record. */
     if (removeMonitor() == FAIL) {
        write an internal error response to the sockio buffer
     }
     else {
        write a positive response to the sockio buffer.
     }

Retrieve monitor updates

There are two stages where monitor retrievals occur. First is the processing of the monitoring request, and second is the handling of the monitor responses. In the first stage, the processing is triggered by a call to the client_recv_hook(). In the second stage, the processing is triggered by repeated calls to the client_send_hook(). At most, only one message can be sent to the client with each client_send_hook() callback function call. As a result, if the client must be notified of many monitor updates, the client_send_hook() will be called multiple times.

The pseudocode processing for how a client ``poll'' request is handled is as follows:


  STAGE 1: Processing initiated when a ``poll'' request is received.

     /* Check to see if the client was sent a mailbox message. */
     /* If not, this would be considered a protocol error,     */
     /* since the client should not be sending a ``poll''      */
     /* request without first being informed with a mailbox    */
     /* message. */
     if (wasMailboxMsgSentToClient == FALSE) {
        write a protocol error message to the sockio buffer
        mark the client object to indicate that a protocol error was sent
        return
     }

     /* Set up the client object fields to indicate that a    */
     /* monitor request is in progress.  This function should */
     /* only fail if the client does not have any monitors    */
     /* defined. */
     if (setMonitorInProgress() == FAIL) {
        write a nothing monitored by client error message to the sockio buffer
        return
     }
     else {
        Call checkSendMonitor() to send monitoring data to the client
     }

  STAGE 2: Processing initiated each time the client receives a 
          client_send_hook() callback function call.

     /* Check to see if monitoring data must be sent to the client */
     checkSendMonitor()

It is important to note that the processing of a directory listing and monitor retrieval is very similar since each case requires an iteration through a list of monitor objects. As a result, it is possible to reuse much of the same code.

Remove an object

The pseudocode processing for how a client object removal request is handled is as follows:


     if (rm message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Try to retrieve the object from the hierarchy.  Both  */
     /* The create parameter and create_valid parameters must */
     /* be set to false in the call to getObject().           */
     if (getObject() == NULL) {
        write object does not exist error response to the sockio buffer
     }

     /* Check to see if the object exists but is marked */
     /* as ``does not exist''. */
     if (isNodeNonexistent() == TRUE) {
        write object does not exist error response to the sockio buffer
        return
     }

     /* Check to see if this client is within the list of    */
     /* clients who have performed a ``touch'' on the object */
     if (touchNodePerformed() == FALSE) {
        write permission denied error response to the sockio buffer
        return
     }

     /* Check whether the object can be cleanly removed */
     /* using the rmObject() function call */
     if (rmObject() == PASS) {
        write a positive response to the sockio buffer
     }
     else {
        write an internal error response to the sockio buffer
     }

Get the current directory path

The pseudocode processing for how a client ``pwd'' request is handled is as follows:


     get the current path with getCurrentPath()
     write current path to the sockio buffer

Change the current directory

The pseudocode processing for how a client ``cd'' request is handled is as follows:


     /* Make a request to change the current directory path */
     if (changeCurrentPath() == FAIL) {
        write directory does not exist error response to the sockio buffer
        return
     }
     else {
        write new directory path response to the sockio buffer
     }

Create directory or register intent to remove a directory

The pseudocode processing for how a client ``touchdir'' request is handled is as follows:


     if (touchdir message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Use the directory path and try to retrieve the     */
     /* directory.  The create parameter to getDir() must  */
     /* be set to TRUE and the create_valid parameter must */
     /* be set to TRUE */
     if (getDir() == FAIL) {
        Status Server internal error
        write a negative response to the sockio buffer
        return
     }
     else {
        if a comment was specified, set it with setNodeComment()
     }
     add the directory to the client touch list using addTouchNode()
     write a positive response to the sockio buffer

Remove a directory

The pseudocode processing for how a client ``rm -r'' request is handled is as follows:


     if (rmdir message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Use the directory path and try to retrieve the     */
     /* directory.  The create parameter to getDir() must  */
     /* be set to FALSE. */
     if (getDir() == FAIL) {
        write a directory not found error response to the sockio buffer
        return
     }

     /* Check to see if this directory is within the list of */
     /* directories the client has performed a ``touch'' on  */
     if (touchNodePerformed() == FALSE) {
        write permission denied error response to the sockio buffer
        return
     }

     /* Try to remove the directory.  At this point, the function  */
     /* should only fail if the directory has valid subdirectories */
     if (rmDir() == PASS) {
        write a positive response to the sockio buffer
     }
     else {
        write directory contains subdirectories error response to the sockio buffer
     }

As it is currently designed, directories will never be completely removed as long as they have data objects within them or subdirectories. In this case it is possible for a directory to have a NONEXISTENT data object within it for the case of a monitor added to a data object, so it will not be possible to remove the directory. Currently, only data objects have a value state of NONEXISTENT while directories are always visible. This should not be a problem with the envisioned model for creating and removing directories to hold FITS header information. If needed, directories could also be given a NONEXISTENT state and effectively hidden from view. This will add some additional complexity in the checks required when removing data objects and directories. Presumably when a data object or directory is removed, some recursive traversal of that tree would need to be performed in order to determine whether directory objects could then be removed more permanently.

Retrieve the contents of a directory

There are two stages where a retrieval of directory contents occurs. First is the processing of the directory retrieval request (``ls''), and second is the handling of the directory responses. In the first stage, the processing is triggered by a call to the client_recv_hook(). In the second stage, the processing is triggered by repeated calls to the client_send_hook(). At most, only one message can be sent to the client with each client_send_hook() callback function call. As a result, if the client must be sent the contents of a large directory, the client_send_hook() will be called multiple times.

The pseudocode processing for how a client ``ls'' request is handled is as follows:


  STAGE 1: Processing initiated when a ``ls'' request is received.

     if (ls message not valid) {
        write a syntax error message to the sockio buffer
        return
     }

     /* Use the dir_path and try to retrieve the directory  */
     /* The create parameter to getDir must be set to FALSE */
     if (getDir() == FAIL) {
        write directory does not exist error response to the sockio buffer
        return
     }

     /* Use the directory object returned by getDir() as  */
     /* the base directory to perform the ls on.  Regular */
     /* expression rules will be applied to each data     */
     /* object name to determine if a match exists.  If   */
     /* so, the object name and value will be stored in a */
     /* linked list.  */
     set up the ls_list of ls monitor objects via setupDirectoryListing()

     /* Set up the client object fields to indicate that an ls */
     /* request is in progress. */
     setLSInProgress()

     write the directory header to the sockio buffer

  STAGE 2: Processing initiated each time the client receives a 
          client_send_hook() callback function call.

     /* Check to see if monitoring data must be sent to the client */
     checkSendLS()

As previously mentioned, the processing of a directory listing and monitor retrieval is very similar since each case requires an iteration through a list of monitor objects. As a result, it is possible to reuse much of the same code.

Initiate a trace

The pseudocode processing for how a client ``trace on'' request is handled is as follows:


     set the global flag indicating that tracing is enabled
     write a positive response to the sockio buffer

Stop a trace

The pseudocode processing for how a client ``trace off'' request is handled is as follows:


     set the global flag indicating that tracing is disabled
     write a positive response to the sockio buffer

Serialize Status Server data to a file

The pseudocode processing for how a client ``autosave'' request is handled is as follows:


     inform client that it has successfully receive the request
     execute the serialize() function with the fork flag set to TRUE

Shutdown the Status Server

The pseudocode processing for how a client ``shutdown'' request is handled is as follows:


     execute the serialize() function with the fork flag set to FALSE
     initiate an exit()

6.2.2 Client Services

This component is responsible for managing the data closely associated with a client connection. Most of the functions manipulate information contained within the client_info_t data structure.

Create a client object

This function creates the client object. Figure 14 contains the default values within the client_info_t structure when the client object is created. The function will return the newly created client_info_t structure for the client.


     client_info_t *createClient(void)

Update a client object

When a client sends the ``register'' command with the program name and UNIX Process ID (PID), this function will be called to add this information to the client_info_t structure associated with the client connection.


     void updateClient(client_info_t *client,
                       const char *prg_name,
                       int pid)

Check if a mailbox message was sent to a client

The client should only retrieve monitor information once it has been informed with a mailbox message that there is monitored data available for retrieval. The following function will return a boolean indicator of whether the client was sent the mailbox message. TRUE indicates that a mailbox message was sent to the client. FALSE indicates that a mailbox message was not sent to the client.


     BOOLEAN wasMailboxMsgSentToClient(const client_info_t *client)

Check if monitor information must be sent to the client

When the client_send_hook() within the Message Handling Services component is called, a call will be made to this function to determine whether monitoring data must be sent to the client. If so, it will either send out the next monitoring update or the EOT message if all monitoring information has been sent to the client.

This function will check whether the is_monitor_in_prog field within the client_info_t structure is set to TRUE. If so, it will start at the monitor_ptr position within the monitor_list and send the next qualifying monitor update to the client. If the monitor_ptr is set to NULL, or there is no more monitoring information to be sent to the client, an EOT message will be sent. Once the EOT message has been sent, the is_monitor_in_prog field will be set to FALSE and the monitor_ptr will be set to NULL.

If the is_monitor_in_prog field is set to FALSE, the is_mbox_empty flag is set to FALSE, and the is_client_notified flag is set to FALSE the client will be sent a mailbox message indicating that there is monitored information ready for retrieval. Once this message has been sent, the is_client_notified flag will be set to TRUE.


     void checkSendMonitor(client_info_t *client)

Set flag indicating monitoring is in progress

When the client_recv_hook() within the Message Handling Services component receives a ``poll'' request, a call will be made to this function to set the appropriate fields to indicate that a monitoring update retrieval command is now in progress.

This function will perform the following actions:


     PASSFAIL setMonitorInProgress(client_info_t *client)

Set flag indicating a directory listing is in progress

When the client_recv_hook() within the Message Handling Services component receives an ``ls'' request, a call will be made to this function to set the appropriate fields to indicate that a directory listing is now in progress.

This function will perform the following actions:


     void setLSInProgress(client_info_t *client)

Check if directory listing information must be sent to the client

When the client_send_hook() within the Message Handling Services component is called, a call will be made to the this function to determine whether directory listing data must be sent to the client. If so, it will either send out the next directory listing message or the EOT message if all directory listing information has been sent to the client.

This function will check whether the is_ls_in_prog field within the client_info_t structure is set to TRUE. If so, it will start at the ls_ptr position within the ls_list and send the next directory listing to the client. If the ls_ptr is set to NULL, or there are no more directory contents to be sent to the client, an EOT message will be sent. Once the EOT message has been sent, the is_ls_in_prog field will be set to FALSE, and the ls_ptr will be set to NULL.


     void checkSendLS(client_info_t *client)

Add a directory object or data object to the client touch list

This function will add a directory object or data object to the linked list used to store objects which this client has performed a touch on. The directory object or data object will only be added to the list if it doesn't already exist in the list.


     void addTouchNode(client_info_t *client,
                       const node_info_t *node)

Check whether a data object or directory object is part of the client touch list

This function will take a directory object or data object and check whether a ``touch'' or ``touchdir'' was previously performed on the object by a client. The function will return TRUE if the object is contained within the linked list of ``touched'' objects. Otherwise, the function will return FALSE.


     BOOLEAN touchNodePerformed(const client_info_t *client,
                                const node_info_t *node)

Add a monitoring object to the client monitor list

This function will add a monitoring object to the linked list used to store monitor objects.


     PASSFAIL addMonitorToClient(client_info_t *client,
                                 const mon_info_t *mon)

Remove a monitoring object from the client monitor list

This function will remove a monitoring object from the linked list used to store monitor objects. If this monitoring object can not be found the function will return FAIL.


     PASSFAIL removeMonitorFromClient(client_info_t *client,
                                      const mon_info_t *mon)

Add a monitoring object to the client ls list

This function will add a monitoring object to the linked list used to store monitor objects for directory listings.


     PASSFAIL addMonitorLS(client_info_t *client,
                           const mon_info_t *mon)

Remove a monitoring object from the client monitor list

This function will remove a monitoring object from the linked list used to store monitor objects for directory listings. If a monitoring object can not be found the function will return FAIL.


     PASSFAIL removeMonitorLS(client_info_t *client,
                              const mon_info_t *mon)

Set the mailbox flag indicating that monitors are available

During object updates or removals, if the monitor criteria has been met to indicate that a client must need to be notified, this function will be called. This function causes the is_mbox_empty flag to be set to FALSE. As a result, a mailbox message will be sent if the is_client_notified flag is also set to FALSE.


     void setMailAvailable(client_info_t *client)

Get the current path associated with a client

This function will return the current path used by the Status Server for relative path references associated with a client connection. The returned path will be represented as an absolute path.


     char *getCurrentPath(const client_info_t *client)

Change the current path associated with a client

This function will change the current directory path associated with a client. The path specified as a parameter can be specified in either relative or absolute path format. In order to determine whether the newly specified directory path is valid, this function will call getDir() in order to identify whether a directory referenced with the new directory path exists. If so, the full_name field within the directory object will be stored in the client object as the new default path for the client. This function will return FAIL if the requested directory path does not point to a directory within the Status Server.


     PASSFAIL changeCurrentPath(const char *new_path)

Remove a client and all of its relevant data

When a client disconnects from the Status Server, the client object and all of its related information must be freed up and removed. This requires that any client objects referenced in the touch_list of a directory object or data object be removed. In addition, all monitor objects associated with the client for both directory listings and data object monitors must be removed. As part of the monitor cleanup, this will require that monitor object references in the monitor_list and ls_list for directory and data objects also be removed. This is the processing which is performed by the removeMonitor() function.


     PASSFAIL removeClient(client_info_t *client)

6.2.3 Data Services

This component is responsible for managing the data associated with the hierarchical structure within which Status Server directory objects and data objects are stored. Most of the functions manipulate information contained within the node_info_t data structure.

Create a directory or register the intent to remove a directory

This function is responsible for creating a directory if it doesn't already exist. If it does exist, the comment associated with the directory will be modified if the comment passed in to the function is not NULL. In order to remove a comment, an empty string must be passed to this function. Finally, a reference to the client object will be stored in the touch_list if it doesn't already exist.


     void touchDir(const node_info_t *base,
                   const char *path,
                   const char *comment,
                   const client_info_t *client,
                   node_info_t **dir)

Retrieve a directory

This function will retrieve a directory object from the hierarchical Status Server directory structure. If the ``create'' parameter is set to TRUE, the directory will be created if it doesn't already exist. As part of the directory creation process, intermediate directories may also be created as part of the process. For example, if the Status Server only has a root directory ``/'' and this function is called to create /fits/455346o, both a fits directory and 455346o directory will be created. All directories will be created with a value of ``DIRECTORY''.

The address of the directory pointer will be returned from this function if it was successful. If the function returns FAIL, more details regarding the failure will be available in cfht_errno.


     PASSFAIL getDir(const node_info_t *base,
                     const char *path,
                     const BOOLEAN create,
                     dir_info_t **dir_p)

Remove a directory

This function will attempt to remove a directory object from the Status Server directory structure. The removal of a directory object is permitted if the client requesting the removal has initiated a ``touchdir'' request for the directory. Otherwise the function will return FAIL. The function will also return fail if the directory object contains any subdirectories. Detailed information regarding a failure will be available in cfht_errno.

If the operation is valid, an attempt will be made to remove each data object from the node_list associated with the directory object. This will be done by calling the rmObject() function with the check_touch parameter set to FALSE.

A check will then be made to determine if the directory object currently has a touch associated with it from another client, or if the size of the node_list is non-zero. If so, the directory object itself can not be completely removed. Otherwise, the directory object will be removed from the parent directory object node_list and deallocated.


     PASSFAIL rmDir(node_info_t *dir,
                    const client_info_t *client)

Add a data object to a directory object

This function adds an object to the linked list of objects associated with the directory object. This operation should not fail unless an object with the same object name already exists in the object list, or an attempt is made to add a data object to another data object instead of a directory object. Detailed information regarding a failure will be available in cfht_errno.


     PASSFAIL addObject(node_info_t *dir,
                        const node_info_t *obj)

Remove a data object from a directory

This function will attempt to remove a data object from the linked list of objects associated with a directory object. If the data object currently has a touch associated with it from another client, or if the data object currently has any monitor objects within its monitor_list or ls_list, the data object will have its value changed to NONEXISTENT. Otherwise, this object does not have any external dependencies and will be removed from the parent directory object node_list and deallocated.

This function should not fail unless it is called on a directory object or if the client object is not contained within the linked list of clients who have performed a ``touch'' on this data object if the check_touch parameter is set to TRUE. Detailed information regarding a failure will be available in cfht_errno.


     PASSFAIL rmObject(node_info_t *obj,
                       const client_info_t *client,
                       BOOLEAN check_touch)

Retrieve a data object

This function will retrieve a data object with a given object_name from the Status Server hierarchy. If a data object with the requested name can not be found, the function will return NULL. Otherwise a pointer to the data object will be returned. This function will iterate through the Status Server hierarchy as needed to find the target object.

If the ``create'' parameter is set to TRUE, the data object will be created if it doesn't already exist along with any required directories. If the ``create_valid'' parameter is set to TRUE and a data object is created, it will be created with a value of UNDEFINED. In addition, the client object will be added to the touch_list associated with the data object and the data object will be added to the touch_list associated with the client object. Otherwise, the object will be created with a value of NONEXISTENT.


     node_info_t *getObject(const char *object_name,
                            const client_info_t *client,
                            const BOOLEAN create,
                            const BOOLEAN create_valid)

Set a directory comment

Convenience function to set the comment associated with a directory or data object.


     void setNodeComment(node_info_t *node,
                         const char *comment)

Serialize Status Server contents to a file

This function will initialize the serialization process for the entire Status Server hierarchical directory structure and all the objects contained within the Status Server. Serialization will be performed by saving a series of transactions which can replayed by the message handler in order to reconstruct the Status Server directory structure and the object information contained within it. The messages will be identical to the ``touch'' and ``touchdir'' requests sent by a client with the exception that they will contain some additional parameters such as object value, value state, update timestamp, and creation timestamps for the objects.

During normal operations, the contents of the Status Server will be serialized at a predefined interval (probably 10 minutes). The Status Server contents will also be serialized is when a client requests a serialization. In both cases, a forked process will be used to save the contents of the Status Server to disk. The only time a fork will not be performed is when a client request a shutdown of the Status Server, or an error condition occurs requiring the Status Server to exit.

This function will initiate recursive processing to traverse the directory structure and initiate serialization for each directory object and data object within the directory structure.

Whenever the Status Server forks a child process to initiate a serialization, the PID of the child process will be saved. If a subsequent request for the Status Server to serialize itself is received before the previously spawned child process has completed, the previous child process will be ``killed'' and a new serialization will be initiated.

Once the serialization contents are successfully written to a file, the UNIX ``mv'' command will be performed to transfer the file contents to the target location. This step is used to prevent the creation of a corrupted or incomplete file from being written to the standard file serialization target location.

If a function should return FAIL, details will be available in cfht_errno.


     PASSFAIL serialize(const char *path, BOOLEAN fork)

Populate a serialized touch string for a directory or data object

This component will populate a pre-allocated string buffer with a serialized ``touchdir'' or ``touch'' command used to restore the directory object or data object upon a restore request. If the function should return FAIL, details will be available in cfht_errno.


     PASSFAIL getSerializeTouch(const node_info_t *dir,
                                char **buffer)

Create a data object

This function will create a data object and associate with it the name, comment and lifetime passed in to the function. Figure 15 contains the default values within the node_info_t structure when the data object is created. The create_valid parameter indicates whether the object should be created with a state of UNDEFINED in the case of TRUE or NONEXISTENT in the case of FALSE.


     void createObject(const char *name,
                       const char *comment,
                       const time_t lifetime,
                       const BOOLEAN create_valid,
                       node_info_t **obj)

Add a monitor to a directory or data object

This function will add a monitor to the linked list of monitor objects (either monitor_list or ls_list). The is_node_monitor parameter will identify which monitor list the monitor object must be added to. This function should not return FAIL unless the monitor already exists in the linked list of monitor objects.


     PASSFAIL addMonitorToNode(node_info_t *node,
                               const mon_info_t *mon,
                               const BOOLEAN is_node_monitor)

Remove a monitor from a directory or data object

This function will remove a monitor from the linked list of monitor objects (either monitor_list or ls_list) within a directory object or data object. The is_node_monitor parameter will identify which monitor list the monitor object must removed from. This operation should not return FAIL unless the monitor object does not exist in the linked list of monitor objects.


     PASSFAIL removeMonitorFromNode(node_info_t *node,
                                    const mon_info_t *mon,
                                    const BOOLEAN is_node_monitor)

Retrieve a monitor from a directory or data object

This function will return a monitoring object from the linked list used to store monitors which have been placed on a data object. Each monitor object within the linked list will be checked to determine if it is associated with the client object passed as a parameter. If it is found, a pointer to the monitoring object will be returned. If not, the function will return NULL.


     mon_info_t *getMonitorByClient(const node_info_t *node,
                                    const client_info_t *client)

Inform monitoring clients of object changes

This function will go through the linked list of monitoring objects associated with the data object and determine whether the monitoring criteria has been met to cause a mailbox message to be sent out to the client. This function should not return FAIL unless it was executed on a directory node.


     PASSFAIL informMonitorClients(const node_info_t *obj)

Setup a monitor listing for a directory

This function will set up a linked list of monitor objects to handle an ``ls'' command request from the client. This function will initiate calls to the createMonitor() function in the Monitor Services component to create monitors for each subdirectory and data object contained within the current directory object. This function should not return FAIL unless it was executed on an data object node.


     PASSFAIL setupDirectoryListing(node_info_t *dir,
                                    const client_info_t *client)

Refresh object state

Since some data objects may have a lifetime associated with them, it is important to update the state of the data object whenever the object becomes expired. This is critical if the object is being monitored. This function will check and, if necessary, update the value of a data object to EXPIRED. If the state is updated, the informMonitorClients() function will be called to handle potential client monitor updates. This function should not return fail unless it was executed on a directory node.

When an object goes from valid to expired, it must be removed from the time dependent object list by calling removeTimeDepObject(). More details regarding the time dependent object list can be found in section 6.2.5.


     PASSFAIL refreshObjectState(node_info_t *obj)

Check whether a directory or data object is valid

This function will return a boolean value indicating whether the value of a directory or data object is valid. This function will return TRUE if the value_state field is set to SS_VALID. Otherwise the function will return FALSE.


     BOOLEAN isNodeValid(const node_info_t *obj)

Check whether a data object does not exist

For monitor purposes, it is necessary to create directory objects and data objects to support monitoring pointer integrity even if the data objects or directories did not previously exist. This function will return TRUE if the value_state field is set to SS_NONEXISTENT. Otherwise the function will return FALSE.


     BOOLEAN isNodeNonexistent(const node_info_t *obj)

Retrieve the value of a directory or data object

This function will return the value of a directory or data object. This value may or may not be valid. In the case of a directory, this will be a string indicating the change count of the directory.


     char *getNodeValue(const node_info_t *node)

Set the value of a directory or data object

This function will set a new value for a directory or data object. If the passed in value is set to NULL, the value of the node will be incremented if it is a directory and UNDEFINED if it is a data object.

In the case of a data object, informMonitorClients() will be called to determine if clients must be notified of a monitor change due to a change in the data object value. In addition, a check is made whether a non-zero lifetime was defined for the data object. If so, the repositionTimeDepObject() function within the Time Dependent Services component will be called to handle the positioning of this object within the linked list of time dependent objects.

If the value of a data object is changing from EXPIRED to a valid value, the addTimeDepObject() function will be called to add this object to the time dependent object list.


     void setNodeValue(node_info_t *node,
                       const char *value)

Set the lifetime of a data object

Convenience function to set the lifetime of a data object. If the lifetime of an object is unlimited, it will be set to 0. This function should not return FAIL unless it was executed on a directory node. If the lifetime of the object changes from unlimited to a valid lifetime, the addTimeDepObject() function must be called in the Time Dependent Services component to add this data object to the linked list of time dependent objects. If the lifetime of the object changes from a valid lifetime to unlimited, it must be removed from the list of time dependent objects with the removeTimeDependentObject() function.


     PASSFAIL setObjectLifetime(node_info_t *obj,
                                const time_t lifetime)

Get the lifetime of a data object

Convenience function to retrieve the lifetime of an object. If this function is called on a directory node, a value of 0 will be returned.


     time_t getObjectLifetime(const node_info_t *obj)

Get the comment associated with a directory or data object

Convenience function to retrieve the comment associated with a directory or data object. This may be NULL if a comment has not been associated with the directory or data object.


     char *getNodeComment(const node_info_t *node)

Set the full path of a directory or data object

This is a convenience function to set the fully-qualified absolute path of a directory or data object. Once an directory or data object is created, the path will never change, since it is not possible to move objects within the Status Server.


     void setFullPath(const node_info_t *node,
                      const char *fullpath)

Get the full path of a directory or data object

This function returns the fully-qualified absolute path of a directory or data object. The path associated with a directory or data object should never be NULL.


     char *getFullPath(const node_info_t *node)

Remove a node

This function will free up all the resources associated a directory or data object and remove it.


     PASSFAIL removeNode(const node_info_t *node)

6.2.4 Monitor Services

This component is responsible for managing the data associated with the Status Server monitors. Most of the functions manipulate information contained within the mon_info_t data structure.

Create a monitor object

This function will create a monitor object. Figure 16 contains the default values within the mon_info_t structure when the monitor object is created.

The type of monitor object which is being created will be identified with the is_data_monitor. Monitor objects can be created to support either client requested object monitors or directory listings. Once a monitor object is created, the client services component and data services components will be used to ensure that the monitoring object is properly associated with the client object and data object. If the return value of the function indicates that the call failed, more details will be available in cfht_errno.


     PASSFAIL createMonitor(node_info_t *object,
                            client_info_t *client,
                            const double deadband,
                            const boolean is_data_monitor)

Update the deadband threshold for a monitor object

This function will update the deadband threshold for an existing monitor object.


     void updateMonitorDeadband(mon_info_t *mon,
                                const double deadband)

Record the object value which has been sent to a client

This function will be called whenever a monitor update is sent to the client. It will set the value sent to the client as well as the prev_sent_ts timestamp indicating when the client was sent the monitoring information.


     void setSentData(mon_info_t *mon,
                      const char *value)

Check whether a client must be notified of a monitoring change

This function will return a boolean value indicating whether the current value of an object has changed enough to warrant a client notification.


     BOOLEAN notifyClient(const mon_info_t *mon)

Remove a monitoring object

This function will free up all the resources associated a monitoring object and remove it. Before a monitor object is removed, references to the monitor object must be removed from the monitor_list and ls_list in the node_info_t object and ls_list and monitor_list in the client_info_t object. This operation should not fail, but if it does, more details regarding the failure are available in cfht_errno.

Although the functionality is not contained within this function call, after a monitor is removed, a check should be made whether the data object being monitored can be removed. Once a monitor is removed, the data object can also be removed if its value is NONEXISTENT and if the size of its touch_list, monitor_list and ls_list are all zero. In this case, the object is already marked as non-existent and does not have any other external references to it.


     PASSFAIL removeMonitor(mon_info_t *mon,
                            const BOOLEAN is_data_monitor)


6.2.5 Time Dependent Services

This component is responsible for managing the the data associated with time dependent objects. A time dependent object is defined as a data object, whose state will change depending upon time. Any data object which has a specified lifetime is considered a time dependent object. The functions in this component manipulate the linked list of time dependent objects.

Add a data object to the list of time dependent objects

This function will take a data object and try to add it to the linked list of time dependent objects. A check will be made whether it is a data object and not a directory and whether the lifetime associated with the data object is not set to zero. Both cases must be true. Also, this function will make sure the object does not already exist in the linked list. If the object already exists, or the previous checks failed, this function will return FAIL. When the object is inserted in the list, it will be added in descending order based on the expiration time of the object.


     PASSFAIL addTimeDepObject(const node_info_t *obj)

Remove a time dependent object from the list of time dependent objects

This function will take a time dependent object and perform a search to see if the data object already exists in the linked list of time dependent objects. If so, it will be removed from the list. If not, the function will return FAIL.


     PASSFAIL removeTimeDepObject(const node_info_t *obj)

Service time dependent objects

This function will iterate through each node in the linked list of time dependent objects which calculations show should have changed to an expired state. The state of the object will be refreshed by calling refreshObjectState(). This function will handle calling removeTimeDepObject() if the object has changed to an expired state as well as triggering client monitor notification processing if needed. Since the list should be maintained in a sorted order by objects which should be the first to expire at the top, it will only be necessary to iterate through the list until an object has time remaining before it expires.


     void serviceTimeDepObjects(void)

Reposition a time dependent object

Whenever a time dependent object is modified, there is a good chance that it must be repositioned within the linked list. This is because based on its lifetime, the time to the next possible expiration of the object may require a different positioning within the list which is sorted in descending order based on the expiration time of the object. This function should not fail unless it is called on a directory object or if the data object is not contained within the list of time dependent objects.


     PASSFAIL repositionTimeDepObject(const node_info_t *obj)

Retrieve the minimum time to next update

This function will return the number of seconds before another item within the list of time dependent objects must be serviced. This time interval will be used as part of the calculation to identify the timeout value to call the sockserv_run() function in the sockio library. This time interval becomes the timeout used to call the underlying socket select function call. This time value can be calculated by determining the time to expiration of the first item in the linked list. If the list does not contain any time dependent objects, a predefined constant time value will be returned.


     int getMinimumTimeoutInterval(void)

6.2.6 Utility Functions

This component contains utility functions which may be used by one or more Status Server software components. Some of the functionality which will be provided by this component include:

Memory Allocation Wrapper Functions

Memory allocation in the Status Server will be handled in such a way that a call to allocate memory will never fail. Wrapper functions will be provided around malloc() and realloc() to prevent a calling function from receiving a NULL pointer indicating that memory could not be allocated. The wrapper functions, ssMalloc() and ssRealloc() will perform an underlying call to malloc() and realloc(). However, if either of these system calls returns a NULL value, the function will sleep for a predefined time interval (maybe a second or so) and retry. The function will not return back a pointer until memory is available. Since the Status Server is single-threaded, this means the server will block until memory becomes available.

Some analysis regarding some of the alternative approaches to handling out-of-memory conditions is included in the Design Analysis section at the end of this document.

Linked List Functions

The node_info_t and client_info_t data structures each contain fields identified as linked lists. A complete set of functions will be provided to manipulate the linked lists. The linked lists in the Status Server will be doubly linked and optionally sorted upon insert.

Argument Parsing and Validation Functions

When a client request is received across the socket interface, it must be checked to determine if it is a valid URL encoded string and, if so, it must be parsed according to its commands and arguments. Parsing functions already exist within libcli to handle some of the basic argument parsing required.

Logging Functions

Error and debug logging will be handled through the logging functions within libcfht. Convenience functions to handle the formatting of debug and error information before cfht_logv is called may be included here.


next up previous contents
Next: 7. Design Analysis Up: Status Server Detailed Design Previous: 5. Client C API   Contents
Tom Vermeulen
2011-01-26