Mar 10

ZendServer 5.6 Virtual Hosts problem

I am in the middle of major upgrade from Windows 7 to Windows 8 on my PC. Apart from a number of issues with IBM software and learning a totally new interface most of it has gone OK!. However one item which was really annoying was the migration of my test PHP environments.

I originally downloaded the latest version of Zend Server 6.0 for Windows which seemed to install OK on Windows 8 but gave me nightmares when trying to migrate my PHP websites over. The main problem is the lack of support from Aura for Zend Server 6.0 on Windows! I did correspond with Aura about when support would be available but at the time of writing we have no news. To overcome this we decided to download and install Zend Server 5.6 instead, the install went OK and the Easycom installer found the Zend install without any problems so we thought were were on a winner! Well that was until we tried to configure the VirtualHost containers.

In Zend Server 5.6, Zend appear to have changed the way that Virtual Hosts are configured, the way that we had been doing it for years seemed to work OK until we tried to connect to the websites from other devices. All we kept getting was the Zend Server test page, obviously something was wrong. When we looked at the configuration for the Apache server we found the following at the bottom of the config file.

Include “conf/zend.conf”
#ZEND-{090AFE0D4ABB5DFDCCB84641FE115680}
NameVirtualHost *:80

#ZEND-{9D752CB08EF466FC480FBA981CFA44A1}
Include “E:\Zend\ZendServer/etc/sites.d/zend-default-vhost-80.conf”
#ZEND-{9D752CB08EF466FC480FBA981CFA44A1}

#ZEND-{090AFE0D4ABB5DFDCCB84641FE115680}
#ZEND-{C5D84B491DE9533AB5043B62C3D41057}
Include “E:\Zend\ZendServer/etc/sites.d/globals-*.conf”
Include “E:\Zend\ZendServer/etc/sites.d/vhost_*.conf”
#ZEND-{C5D84B491DE9533AB5043B62C3D41057}

Now obviously the ‘#’ generally denotes a comment so we should be able to ignore those lines, but the other lines are what caused the issue. The zend.conf file contains the virtual host definitions for the GUI which is run on port 10081 so we could understand what was going on there. The next line seemed to bring in another virtual host configuration, and finally the “Include “E:\Zend\ZendServer/etc/sites.d/vhost_*.conf”" line brought in what should have been our defined virtual hosts.
Our configuration file called vhosts_shield.conf was added to the directory ‘E:\Zend\ZendServer/etc/sites.d’ so according to the config file we believed that it would be added to the Virtual Host configuration, but it was still refusing to server up the sites. After some review and a lot of searching the web (even the Zend documentation and forums) we could not find out where we should place the virtual host content for it to be served up correctly. We would always be sent the default Zend page by the server regardless of the url entered. At first we tried to amend the zend-default-vhost to point to our own directory structure (this is the same as we have always set up) but that kept stopping the server from starting. Then it dawned on us, the configuration file seemed to be forcing the Zend page and ignoring our own vhosts file even though we believed it was being loaded. So we decided to force the default VirtualHost to run against a a nondescript port and only have our virtual host file (sites.d/vhosts_shield.conf) running against port 80. That was it, simply changing the port configured in the httpd.conf file for the VirtualHhost made it work.

So if you have moved to Zend 5.6 or above and you are using VirtualHosts the above will work for you. Eventually we will find out where Zend likes to have the site content stored and how the config files should be set up, but for now we are OK. The Aura incompatibility with ZendServer 6.0 needs to be resolved as well, as soon as we know when Aura will support this configuration we will certainly add a new post.

Happy PHP’ing..

Chris..

Feb 12

Adding a Bar Graph for CPU Utilization to HA4i and DR4i

As part of the ongoing improvements to the PHP interfaces we have developed for HA4i and DR4i we decided to add a little extra to the dashboard display. The dashboard display is a quick view of the status for the replication processes, we used traffic lights as indicators to determine if the processes are running OK. Another post recently discussed using a gauge to display the overall CPU utilization for the system but we wanted to take it one step further, we wanted to be able to show just the CPU utilization for the HA4i/DR4i jobs.

We thought about how the data should be displayed and settled on a bar graph, the bars of the graph would represent the CPU utilization as a percentage of the available CPU and would be created for each job that was running in the HA4i/DR4i subsystem. This gave us a couple of challenges because we needed to determine just how many jobs should be running and then allow us to build a table which would be used to display the data. There are plenty of bar graph examples out there which show how to use CSS and HTML to display data, our only difference is that we would need to extract the data from the system and then build the bar graph based on what we were given.

The first program we needed to create was one which would retrieve the information about the jobs that are running that could be called from the Easycom program interface. We have already published a number of tests around this technology so we will just show you the code we added to allow the data to be extracted. To that end we extended the PHPTSTSRV service program with the following function.

typedef _Packed struct job_sts_info_x {
char Job_Name[10];
char Job_User[10];
char Job_Number[6];
int CPU_Util_Percent;
} job_sts_info_t;

int get_job_sts(int *num_jobs, job_sts_info_t dets[]) {
int i,rc = 0,j = 0; /* various ints */
int dta_offset = 0;
char msg_dta[255]; /* message data */
char Spc_Name[20] = "QUSLJOB QTEMP "; /* space name */
char Format_Name[8] = "JOBL0100"; /* Job Format */
char Q_Job_Name[26] = "*ALL HA4IUSER *ALL "; /* Job Name */
char Job_Type = '*'; /* Job Info type */
char *tmp;
char *List_Entry;
char *key_dta;
Qus_Generic_Header_0100_t *space; /* User Space Hdr Ptr */
Qus_JOBL0100_t *Hdr;
Qwc_JOBI1000_t JobDets;
EC_t Error_Code = {0}; /* Error Code struct */

Error_Code.EC.Bytes_Provided = _ERR_REC;
/* get usrspc pointers */
/* memcpy(Q_Job_Name,argv[1],26); */
QUSPTRUS(Spc_Name,
&space,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
if(memcmp(Error_Code.EC.Exception_Id,"CPF9801",7) == 0) {
/* create the user space */
if(Crt_Usr_Spc(Spc_Name,_1MB) != 1) {
printf(" Create error %.7s\n",Error_Code.EC.Exception_Id);
exit(-1);
}
QUSPTRUS(Spc_Name,
&space,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("Pointer error %.7s\n",Error_Code.EC.Exception_Id);
exit(-1);
}
}
else {
printf("Some error %.7s\n",Error_Code.EC.Exception_Id);
exit(-1);
}
}
QUSLJOB(Spc_Name,
Format_Name,
Q_Job_Name,
"*ACTIVE ",
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("QUSLJOB error %.7s\n",Error_Code.EC.Exception_Id);
exit(-1);
}
List_Entry = (char *)space;
List_Entry += space->Offset_List_Data;
*num_jobs = space->Number_List_Entries;
for(i = 0; i < space->Number_List_Entries; i++) {
Hdr = (Qus_JOBL0100_t *)List_Entry;
memcpy(dets[i].Job_Name,Hdr->Job_Name_Used,10);
memcpy(dets[i].Job_User,Hdr->User_Name_Used,10);
memcpy(dets[i].Job_Number,Hdr->Job_Number_Used,6);
QUSRJOBI(&JobDets,
sizeof(JobDets),
"JOBI1000",
"*INT ",
Hdr->Internal_Job_Id,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
printf("QUSRJOBI error %.7s\n",Error_Code.EC.Exception_Id);
exit(-1);
}
dets[i].CPU_Util_Percent = JobDets.CPU_Used_Percent;
List_Entry += space->Size_Each_Entry;
}
return 1;
}

The program calls the QUSLJOB API to create a list of the jobs which are being run with a User profile of HA4IUSER (we would change the code to DR4IUSER for the DR4I product) and then use the QUSRJOBI API to get the CPU utilization for each of the jobs. We did consider using just the QUSLJOB API with keys to extract the CPU usage but the above program does everything we need just as effectively. As each job is found we are writing the relevant information to the structure which was passed in by the PHP program call.

The PHP side of things requires the i5_toolkit to call the program but you could just as easily (well maybe not as easily :-)) use the XMLSERVICE to carry out the data extraction. We first created the page which would be used to display the bar chart, this in turn calls the functions required to connect to the IBMi and build the table to display the chart. Again we are only showing the code which is additional to the code we have already provided in past examples. First this is the page which will be requested to display the chart.

<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/
// start the session to allow session variables to be stored and addressed
session_start();
require_once("scripts/functions.php");
// load up the config data
if(!isset($_SESSION['server'])) {
load_config("scripts/config_1.conf");
}
$conn = 0;
$_SESSION['conn_type'] = 'non_encrypted';
if(!connect($conn)) {
if(isset($_SESSION['Err_Msg'])) {
echo($_SESSION['Err_Msg']);
$_SESSION['Err_Msg'] = "";
}
echo("Failed to connect");
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<style>
td.value {
background-image: url(img/gl.gif);
background-repeat: repeat-x;
background-position: left top;
border-left: 1px solid #e5e5e5;
border-right: 1px solid #e5e5e5;
padding:0;
border-bottom: none;
background-color:transparent;
}

td {
padding: 4px 6px;
border-bottom:1px solid #e5e5e5;
border-left:1px solid #e5e5e5;
background-color:#fff;
}

body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 80%;
}

td.value img {
vertical-align: middle;
margin: 5px 5px 5px 0;
}

th {
text-align: left;
vertical-align:top;
}

td.last {
border-bottom:1px solid #e5e5e5;
}

td.first {
border-top:1px solid #e5e5e5;
}

table {
background-image:url(img/bf.png);
background-repeat:repeat-x;
background-position:left top;
width: 33em;
}

caption {
font-size:90%;
font-style:italic;
}
</style>
</head>

<body>
<?php get_job_sts($conn,"*NET"); ?>
</body>
</html>

The above code shows the STYLE element we used to form the bar chart, normally we would encompass this within a CSS file and include that file, but in this case as it is just for demonstrating the technology we decided to leave it in the page header. the initial code of the page starts up the session, includes the functions code, loads the config data which is used to make the connection to the IBMi and then connects to the IBMi. Once that is done the function which is contained in the functions.php called get_job_sts is called. Here is the code for that function.


/*
* function to display bar chart for active jobs and CPU Usage
* @parms
* the connection to use
*/

function get_job_sts(&$conn,$systyp) {
// get the number of jobs and the data to build the bars for

$desc = array(
array("Name" => 'NumEnt', "io" => I5_INOUT, "type" => I5_TYPE_INT),
array("DSName" =>"jobdet", "count" => 30, "DSParm" => array(
array("Name" => "jobname", "io" => I5_OUT, "type" => I5_TYPE_CHAR, "length" => "10"),
array("Name" => "jobuser", "io" => I5_OUT, "type" => I5_TYPE_CHAR, "length" => "10"),
array("Name" => "jobnumber", "io" => I5_OUT, "type" => I5_TYPE_CHAR, "length" => "6"),
array("Name" => "cpu", "io" => I5_OUT, "type" => I5_TYPE_INT))));
// prepare for the program call
$prog = i5_program_prepare("PHPTSTSRV(get_job_sts)", $desc, $conn);
if ($prog == FALSE) {
$errorTab = i5_error ();
echo "Program prepare failed <br>\n";
var_dump ( $errorTab );
die ();
}
// set up the input output parameters
$parameter = array("NumEnt" => 0);
$parmOut = array("NumEnt" => "nbr", "jobdet" => "jobdets");
$ret = i5_program_call($prog, $parameter, $parmOut);
if (!$ret) {
throw_error("i5_program_call");
exit();
}
echo("<table cellspacing='0' cellpadding='0' summary='CPU Utilization for HA4i Jobs'>");
echo("<caption align=top>The current CPU Utilization for each HA4i Job on the " .$systyp ." System</caption>");
echo("<tr><th scope='col'>Job Name</th><th scope='col'>% CPU Unitlization</th></tr>");
for($i = 0; $i < $nbr; $i++) {
$cpu = $jobdets[$i]['cpu']/10;
if($i == 0) {
echo("<tr><td class='first' width='20px'>" .$jobdets[$i]['jobname'] ."</td><td class='value first'><img src='img/util.png' alt='' width='" .$cpu/2 ."%' height='16' />" .$cpu ."%</td></tr>");
}
elseif($i == ($nbr -1)) {
echo("<tr><td class='last' width='20px'>" .$jobdets[$i]['jobname'] ."</td><td class='value last'><img src='img/util.png' alt='' width='" .$cpu/2 ."%' height='16' />" .$cpu ."%</td></tr>");
}
else {
echo("<tr><td width='20px'>" .$jobdets[$i]['jobname'] ."</td><td class='value'><img src='img/util.png' alt='' width='" .$cpu/2 ."%' height='16' />" .$cpu ."%</td></tr>");
}
}
echo("</table>");
return 1;
}

The program call is prepared with a maximum of 30 job info structures, we would normally look to define this before the call and set the actual number of jobs to extract but for this instance we simply decided that 30 structures would be more than enough. After the program is called and the data returned we then build the table structure that will be used to display the data. We originally allowed the bar to take up all of the table width but after testing on our system which has uncapped CPU found that we would sometimes get over 100% CPU utilization. We still show the actual utilization but decided to halve the bar width which gave us a better display.

HA4i is running on our system in test so the CPU utilization is pretty infrequent even when we run a saturation test, but the image capture below will give you an idea of what the above code produces in our test environment.

CPU_Bar_Chart

CPU Ultization Bar Chart HA4i

Now we just need to include the relevant code into the HA4i/DR4i PHP interfaces and we will be able to provide more data via the dashboard which should help with managing the replication environment. You can see the original bar chart on which this example was produced here

Happy PHP’ing.

Chris…

Feb 07

Slow Response with i5_pconnect().


While experimenting with the latest version of our DR4i php interface we came across a slight issue with the i5_connection routines. The problem only appeared after we moved the code from our PC testing environment to the iAMP install so we thought it was simply a slow down as we moved from the PC to the IBMi, unfortunately this is only part of the problem. As soon as we found the issue we contacted Aura and asked them for support, they came back asking about how the problem was manifesting itself as they have not seen it elsewhere and were not sure what could be causing the problem.

We asked Aura about the code and what could have changed to cause the significant slow down, they said that nothing had changed and because they were not able to recreate the same issue in their network they could not understand why we were. After some further discussion and discovery they let us know that they had moved away from the gethostbyname() API to the getaddrinfo() API in preparation for IPV6 support. getaddrinfo() is the API which should be used in place of gethostbyname() API where IPV6 support is required.

We scoured the internet and found a number of entries which discussed the slowdown of lookups when getaddrinfo() was used. It was obviously a problem and we needed to understand how this was playing a part in our environment but not in Aura’s. So our first action was to write a test program which would take a host name and try to resolve that using the getaddrinfo() API. Here is the code we started off with.


#include
#include
#include
#include
#include
#include /* CEE date functions */

#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif

int main(int argc,char **argv) {
int error;
int junkl; /* Int holder */
double secs; /* Secs holder */
char Time_Stamp[18]; /* Time Stamp holder */
char hostname[NI_MAXHOST] = ""; /* Host name returned */
unsigned char junk2[23]; /* Junk char string */
struct addrinfo *result;
struct addrinfo *res;

CEELOCT(&junkl, &secs,junk2,NULL);
CEEDATM(&secs,"YYYYMMDDHHMISS999",Time_Stamp,NULL);
printf("Start = %s\n",Time_Stamp);
error = getaddrinfo(argv[1], NULL, NULL, &result);
/* time now */
CEELOCT(&junkl, &secs,junk2,NULL);
CEEDATM(&secs,"YYYYMMDDHHMISS999",Time_Stamp,NULL);
printf("After getaddrinfo = %s\n",Time_Stamp);
if(error != 0) {
fprintf(stderr, "error in getaddrinfo: %s\n", gai_strerror(error));
exit(EXIT_FAILURE);
}
/* loop over all returned results and do inverse lookup */
/* loop over all returned results and do inverse lookup */
for(res = result; res != NULL; res = res->ai_next) {
error = getnameinfo(res->ai_addr,
res->ai_addrlen,
hostname,
NI_MAXHOST,
NULL,
0,
0);
if(error != 0) {
fprintf(stderr, "error in getnameinfo: %s\n", gai_strerror(error));
}
if(*hostname != '\0')
printf("hostname: %s\n", hostname);
CEELOCT(&junkl, &secs,junk2,NULL);
CEEDATM(&secs,"YYYYMMDDHHMISS999",Time_Stamp,NULL);
printf("After getnameinfo = %s\n",Time_Stamp);
}
freeaddrinfo(result);
return 0;
}

When we ran this test program against our network with a simple hostname which is defined in our HOST file here is a sample of the output.

Start = 20130205095444797
After getaddrinfo = 20130205095453000
hostname: SHIELD3.SHIELD.LOCAL
After getnameinfo = 20130205095453000
Press ENTER to end terminal session.

This showed an 8 second response time for the getaddrinfo() API! Obviously this would not be acceptable as it would be used each time a connection was made. This was an issue because we do not have a DNS to resolve our local names and instead rely on the HOST table entries, our default search is set to *LOCAL so we would have expected getaddrinfo() to look up the address in the HOST table first and it would have been resolved. But due to the way the API has been coded it was always going out to the DNS server asking for an IPV6 address before looking for the IPV4 address in the HOST table.

We then looked at the documentation a lot closer and after some experimentation found that if we removed the Domain Information from the TCP/IP setup (option 12 on the CFGTCP menu) we could get the request for a server name back to immediate responses, but as soon as we added Domain information such as ‘shield3.shield.local’ the response time would instantly creep back up to over 8 seconds. Again not acceptable as the environment we needed the fix for is using NamedVirtualHosting which would always pass in a FQDN.

This is when we raised a PMR with IBM and supplied them with all of the data we had been using and asked for support. They came back with a link to a document which described the problem exactly and it was only affecting i/OS from V6R1 onwards. Because from V6R1 onwards IBM had implemented the getaddrinfo() API to do IPV6 lookups first it would always go out to the DNS for a name resolution even if an IPV4 address could be resolved from the HOST file! It would only drop back to a IPV4 lookup after the IPV6 lookup had failed!

The answer in the end was very simple, we just had to code up the AI_ADDRCONFIG flag in the getaddrinfo() request and it would only do an IPV6 lookup if more than 1 IPV6 address had been configured (::1 is not considered a configured IPV6 address). Now we see immediate responses from the API and everything works as it should even with the Domain Information configured.

If you are seeing a dramatic slowdown in your TCP/IP connection after migrating to V6R1 and you or your aplication vendor are using the getaddrinfo() API you may want to consider the above. Easycom connection routines are affected at the moment but a fix is being developed to resolve the issue.

Chris…

Dec 18

New features added to HA4i

A couple of new features have been added to the HA4i product as a result of customer requests. Auditing is one area where HA4i has always been well supported but as customers get used to the product they find areas they would like to have some adjustments. The object auditing process was one such area, the client was happy that the results of the audits were correct but asked if we could selectively determine which attributes of an object are to be audited as they have some results which while they are correct are not important to them.

The existing process was a good place to start so we decided to use this as the base but while were making changes improve the audit to bring in more attributes to be checked. We determined a totally new set of programs would be required which would include new commands and interfaces, this would allow the existing audit process to remain intact where clients have already programmed them into their schedulers and programs. The new audits would run by retrieving the list of parameters to be checked from a control file and only compare configured parameters. The results have been tested by the client and he has given us the nod to say this meets with his approval. We also added new recovery features which allow out of sync objects to be fully repaired more effectively.

Another client approached us with a totally different problem, they were having problems with errors being logged from the journal apply process due to developers saving and restoring journaled objects from the production environment into test libraries on the production system. This caused a problem because the objects are automatically journaled to the production journal when they are restored, so when the apply process finds the entry in the remote journal it tries to create the object on the target system and fails because the library does not exist. To overcome this we amended the code which supports the re-direction technology for the remote apply process (It allows journal entries for objects in one library to be applied to objects in another library) to support a new keyword *IGNORE. When the apply process finds these definitions it will automatically ignore any requests for objects in the defined library. NOTE:- The best solution would have been to move the developers off the production systems and develop more HA friendly approaches to making production data available, but in this case that was not an option.

We are always learning and adding new features into HA4i, many of them from customer requirements or suggestions. Being a small organization allows us to react very quickly to these requirements and provide our clients with a High Availability Solution that meets their needs. If you are looking for an Affordable High Availability or Disaster Recovery Solution or would like to ask about replacing an existing solution give us a call. We are always happy to look at your needs and see if HA4i will fit your solution requirements and budget.

Chris…

Dec 13

Linking IBMi data to a Gauge in PHP

We were thinking about how to create a new interface for one of our old utilities using PHP and decided that using a JavaScript based gauge would probably be a good start. There are plenty of free and chargeable JavaScript utilities out there that would do what we wanted, the one we settled on was a HTML5 Canvas implementation developed by Mykhailo Stadnyk. He has placed the code on the web and agreed to anyone running the code under a MIT license which basically allows you to copy and use the code where ever you want. If you would like to use the code it is available here, simply copy the javascript file into your directory structure and include.

We made a couple changes to the javascript code as it calls a remote HTTP server to pull back a font so we found a substitute font and installed it on the PC. There are a couple of examples available which can be used to demonstrate the gauge in action which again we used as the basis for our test. The only other point we should mention is that the demo does rely on HTML5 and canvas, if it is not supported in your browser the test will not work!

The method used to get the data from the IBMi is to call a Service Program through the i5_program_call available in the Aura i5_toolkit. Below is the C Program to return the information, it just calls the QWCRSSTS API and returns the Pct_Processing_Unit_Used value as can be seen in the code below.


#include <stdio.h> /* Standard I/O */
#include <stdlib.h> /* Standard library */
#include <string.h> /* String handlers */
#include <qusec.h> /* Error Code */
#include <errno.h> /* Error Num Conversion */
#include <decimal.h> /* Decimal support */
#include <qusec.h> /* Error Code */
#include <qwcrssts.h> /* System Status */
#pragma comment(copyright,"Copyright @ Shield Advanced Solutions Ltd 1998-2001")

typedef _Packed struct EC_x{
Qus_EC_t EC;
char Exception_Data[1024];
} EC_t;

int Get_Svr_Status(char *CPU_Util,char *reset) {
double Cpu_Pct;
char Reset[10] = "*NO "; /* Reset CPU % */
Qwc_SSTS0200_t Buf; /* System Status Struct */
EC_t Error_Code = {0}; /* Error Code Struct */

Error_Code.EC.Bytes_Provided = sizeof(Error_Code);
// reset the CPU Utilization?
if(*reset == 'Y')
memcpy(Reset,"*YES ",10);
QWCRSSTS(&Buf,
sizeof(Buf),
"SSTS0200",
Reset,
&Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
sprintf(CPU_Util,"99999");
return -1;
}
// Just set up the CPU Utilization here
Cpu_Pct = (double)Buf.Pct_Processing_Unit_Used / 10;
sprintf(CPU_Util,"%f",Cpu_Pct);
return 1;
}

The web page is generated using the following code.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/
// start the session to allow session variables to be stored and addressed
session_start();
require_once("scripts/functions.php");
// load up the config data
if(!isset($_SESSION['server'])) {
load_config();
}
$conn = 0;
$_SESSION['conn_type'] = 'non_encrypted';
if(!connect($conn)) {
if(isset($_SESSION['Err_Msg'])) {
echo($_SESSION['Err_Msg']);
$_SESSION['Err_Msg'] = "";
}
echo("Failed to connect");
}
// get the information
get_cpu_util($conn);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html style="width:100%;height:100%">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Gauge Test</title>
<script src="jscripts/gauge.js"></script>
<style>body{padding:0;margin:0;background:#222}</style>
</head>
<body style="width:100%;height:100%">
<canvas id="gauge"></canvas>
<div id="console"></div>
<script>
var gauge = new Gauge({
renderTo : 'gauge',
width : document.body.offsetWidth,
height : document.body.offsetHeight,
glow : true,
units : 'Cpu Utilization',
title : false,
minValue : 0,
maxValue : 110,
majorTicks : ['0','10','20','30','40','50','60','70','80','90','100','110'],
minorTicks : 2,
strokeTicks : false,
highlights : [
{ from : 0, to : 50, color : 'rgba(240, 230, 140, .25)' },
{ from : 50, to : 70, color : 'rgba(255, 215, 0, .45)' },
{ from : 70, to : 90, color : 'rgba(255, 165, 0, .65)' },
{ from : 90, to : 100, color : 'rgba(255, 0, 0, .85)' },
{ from : 100, to : 110, color : 'rgba(178, 34, 34, .99)' }
],
colors : {
plate : '#fff',
majorTicks : '#f5f5f5',
minorTicks : '#ddd',
title : '#fff',
units : '#0bb',
numbers : '#0aa',
needle : { start : 'rgba(240, 128, 128, 1)', end : 'rgba(255, 160, 122, .9)' }
}
});
gauge.onready = function() {
gauge.setValue( <?php echo($_SESSION['CPU_Util']); ?> );
};

gauge.draw();

window.onresize= function() {
gauge.updateConfig({
width : document.body.offsetWidth,
height : document.body.offsetHeight
});
};
</script>
<?php ?>
</html>

To call the Service Program we created a function ‘get_cpu_util()’ which is called every time the page is refreshed. Here is the code for the get_cpu_util function.


function get_cpu_util($conn) {
$desc = array (array ("Name" => "CPU_Util", "io" => I5_INOUT, "type" => I5_TYPE_CHAR, "length" => "5"),
array ("Name" => "Reset_Status", "io" => I5_IN, "type" => I5_TYPE_CHAR, "length" => "1") );
$prog = i5_program_prepare("PHPTSTSRV(Get_Svr_Status)", $desc,$conn);
if ($prog == FALSE) {
$errorTab = i5_error ();
echo "Program prepare failed Display_Server_Status <br>";
var_dump($errorTab);
var_dump($conn);
die ();
}
$parmOut = array("CPU_Util" => "cpu_util");
$parameter = array("CPU_UTIL" => "", "Reset_Status" => "N");
$ret = i5_program_call($prog, $parameter, $parmOut);
if (!$ret) {
throw_error("i5_program_call failed Retrieve_Status <br>");
exit();
}
// close the program call
i5_program_close($prog);
$_SESSION['CPU_Util'] = $cpu_util;

return 1;
}

The majority of the work is done in the Javascript section above with just the data extraction from the IBMi being carried out using the i5_toolkit. We have used the same connection function that we used in our other tests but instead of asking for the profile and password we added it to the config file so no sign on screen is presented before the connection is made.

The above code resulted in the following output on our systems. The actual CPU utilization differs because it is near impossible to request the page and refresh the 5250 screen at the same time. We also noticed that the returned value is always greater than the actual value shown in the WRKACTJOB screens, but the test was more about showing the gauge working using IBMi data than ensuring we saw the same data in both interfaces.

5250 Work with Active Jobs

5250 Work with Active Jobs

Gauge showing CPU Utilization %

Gauge showing CPU Utilization %

We feel this is where modernization of the IBMi and its applications should begin, making simple tools and utilities that use the IBMi data and display it in a web based interface means you can access those interfaces from many devices. We only show the output in a PC based browser but with some simple CSS and checking code you could make sure it is correctly displayed on most devices.

Happy PHP’ing.

Chris..

Dec 07

Adding configuration Capabilities to the HA4i PHP Interfaces

We have been developing the management interface for HA4i our High Availability product in PHP for some time, but we had not got round to looking at how we could extend that interface to allow us configure the various elements of HA4i. While the existing 5250 (Green Screen) configuration panels are very effective in what they do but we wanted bring that capability into the PHP interface.

Until now, to view or update the existing configurations you needed to sign onto each system and go through each individual panel group to update the various elements that configure HA4i. We wanted to pull all of that information into a single screen where you could view the existing configuration and select a particular element to be updated.

The first panel displays the existing configuration with links (buttons) to allow you to update the particular configuration.

Here is a sample of the initial page.

New Configuration Screen

New Configuration Screen

The above screen allows each of the elements which can be updated, if a change occurs which requires the configuration to be replicated between the systems it is automatically handled in the same manner it was with the 5250 screens. We like the fact that we can now update any configuration and restart the processes all from one interface. Selecting the option to update the remote journal configuration will display a list of all available remote journals as can be seen in the sample display below.

Remote Journals available for configuration

Remote Journals available for configuration

As you can see from the display, if the journal is already configured it will be identified and a remove button provided, if it is not already configured a button allows it to be added. Because the remove button simply removes the configuration data it does not need any additional panels, but to Configure requires some of the parameters to be presented so they can be changed if required as can be seen in the sample display below.

Configure a new Remote Journal

Configure a new remote journal

Once submit is pressed on this page the remote journal is added to the configuration and the relevant objects created to allow processing by HA4i. Once this completes the list of configured remote journals is displayed again with the new journal correctly identified.

HA4i Object replication is simply a list of libraries that are to be monitored for changes, we decided to use a selection list to allow the required libraries to be configured and a list of existing configurations. You can select as many libraries as you wish in a single request which are automatically added to the configured list and all objects in the requested libraries marked for auditing so that any changes to the objects are captured and replicated. The display below shows the currently configured library plus a scrollable selection list of those libraries which are not.

Configure the libraries Object Replication

Configure the libraries for Object Replication

Output Queues have a similar interface which lists all available out queues for configuration in the same manner as the list of libraries. The following is a sample display from our test system.

Configure Outq's

Configure Outq'

The default configuration is unique to each system so there are two buttons one for each system to allow the configuration to be updated, we provided drop down options for some of the parameters to ensure the data entered is correct plus any parameters which are restricted are set to be read only. This is what our test configuration looks like.

Configure default settings

Configure default settings

That is all you need to configure HA4i, we do have a couple of filter options and IFS replication at the object level which need some attention but in the main this new interface allows you to configure, control and monitor HA4i from a single interface. The biggest gain for us was the speed at which we could implement changes using PHP and Easycom, when we developed the UIM based interfaces it would take us days just to create a single configuration interface. With Easycom and PHP we managed to build the configuration interface in just over a day.

HA4i is a premier High Availability Solution, we are constantly improving the product and interfaces making it simpler to manage and providing many new features. If you are looking at a new High Availability implementation or would like to discuss replacing an existing solution let us know, you may be surprised at just how easy and affordable our High Availability Solution can be.

Chris…

Dec 05

New and improved RTVDIRSZ utility

We were recently working with a partner who needed to asses the size of the IFS directories in preparation for replication with HA4i. Before he could start to plan he needed to understand just how large the IFS objects would be and how many objects would need to be journaled. One of the problems he faced was the save of the IFS had been removed from the normal save because it was so large and took too long to carry out.

We had provided the RTVDIRSZ utility sometime ago which would walk through the IFS from a given path and report back to the screen the number of objects found and the total size of those objects. Running the original RTVDIRSZ request took a number of hours to complete and while it gave him the total numbers he would have liked to see a bit more detail in how the directories were constructed.
So we decided to change the programs a little bit and instead or writing the data back out to the screen we would write it to an IFS directory file which could be viewed at leisure and analyzed further should it be required. As part of the update we also changed the information which would be stored in the file, we added a process to show the directory being processed and what size the directory was as well as the number of objects in that directory. Once all of the information had been collected we wrote out the total data just as we had previously.

Here is a sample of the output generated from our test system.

Browse : /home/rtvdirsz/log/dir.dta
Record : 1 of 432 by 18 Column : 1 144 by 131
Control :

….+….1….+….2….+….3….+….4….+….5….+….6….+….7….+….8….+….9….+….0….+….1….+….2….+….3.
************Beginning of data**************
Directory Entered = /home
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/.manager objects = 4 size = 178.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/manifests objects = 83 size = 57.8kB
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/33/1/.cp/nl/es objects = 5 si
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/33/1/.cp/nl objects = 0 size
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/33/1/.cp objects = 0 size = 0
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/33/1 objects = 0 size = 0.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/33 objects = 0 size = 0.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/36/1/.cp/nl/hr objects = 2 si
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/36/1/.cp/nl objects = 0 size
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/36/1/.cp objects = 0 size = 0
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/36/1 objects = 0 size = 0.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/36 objects = 0 size = 0.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/37/1/.cp/nl/hu objects = 2 si
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/37/1/.cp/nl objects = 0 size
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/37/1/.cp objects = 0 size = 0
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/37/1 objects = 0 size = 0.0B
Directory = /home/QIBMHELP/.eclipse/org.eclipse.platform_3.2.2/configuration/org.eclipse.osgi/bundles/37 objects = 0 size = 0.0B
…….
Directory = /home/ha4itst/exclude/dir1 objects = 3 size = 39.0B
Directory = /home/ha4itst/exclude objects = 1 size = 29.0B
Directory = /home/ha4itst/newdir1/newdir2 objects = 2 size = 1.9kB
Directory = /home/ha4itst/newdir1 objects = 0 size = 0.0B
Directory = /home/ha4itst objects = 1 size = 16.5kB
Directory = /home/QWEBQRYADM objects = 1 size = 18.0B
Directory = /home/ftptest/NEWDIR/test1 objects = 0 size = 0.0B
Directory = /home/ftptest/NEWDIR objects = 0 size = 0.0B
Directory = /home/ftptest objects = 0 size = 0.0B
Directory = /home/jsdecpy/log objects = 1 size = 237.0kB
Directory = /home/jsdecpy objects = 0 size = 0.0B
Directory = /home/rtvdirsz/log objects = 1 size = 49.9kB
Directory = /home/rtvdirsz objects = 0 size = 0.0B
Directory = /home objects = 0 size = 0.0B
Successfully collected data
Size = 442.3MB Objects = 1541 Directories = 427
Took 0 seconds to run

************End of Data********************

Unfortunately the screen output cannot contain all of the data but you get the idea.. Now you can export that data to a CSV file or something and do some analysis on the results found (finding the directory with the most objects or the biggest size etc.)

The utility is up on our remote site at the moment, if I get chance I will move it to the downloads site.
If you are looking at HA and would like to use the utility to understand your IFS better let me know. we can arrange for copies to be emailed to you.

Chris…

Nov 28

Capturing the return value from a function

As a C programmer I have become used to using the return values set by a function as an indicator of the success of the function to carry out its intended role, so when I created Service programs I used the same process. This raised a question about how I could capture that return value and use it within my PHP scripts as an indicator. I have read through the manuals and could not find a way to capture that value so I had resulted to creating a parameter that I would pass in and have that set on return instead of relying on the return value from the function. It seems that there is a way to capture the return value from the function, although it is not documented Easycom provides a simple way to capture the return value via a “virtual parameter”. Why is this important, well we use C a lot, so it is important that when we write a module that we can use it across multiple programs. Before we found this gem we had to write a function which we could call from our C programs and use the return value and another function with exactly the same process that used the input parameters as a way of determining the functions sucess.

Before we go whole hog into this we wanted to check to make sure that our understanding was correct, its not documented so we had to work out some of the details ourselves although Aura did give us the basic structure. The C function we will test with is a service program with a single function, it will return the index of the passed in character within an array of characters (very simple check but provided us with all we need). Here is the C program we compiled as a service Program.


#include<stdio.h>
#include<string.h>

int retvaltst(char *tstchar) {
int i = 0;
char sample[10] = "ABCDEFGHIJ";

for(i = 0; i < 10; i++) {
if(sample[i] == *tstchar)
break;
}
return ++i;
}

As you can see all it does is look at the character passed as looks for it in the sample array, if it finds the character it breaks and returns the index of the character (we increment the counter before we return it). This is the code that we used in a PHP function to test the theory, the connection and everything else is a hack of the connection routines etc we created in previous tests using the PHPTST website..


function get_ret_val(&$conn,$tstchar) {

$description = Array (array ("Name" => "tstchar", "IO" => I5_IN, "Type" => I5_TYPE_CHAR, "Length" => "1"),
array ("Name" => "retval", "IO" => I5_RETVAL | I5_OUT, "Type" => I5_TYPE_INT ));
$prog = i5_program_prepare("CHLIB/RETVALTST(retvaltst)", $description );
if ($prog == FALSE) {
$errorTab = i5_error ();
echo "Program prepare failed RETVALTST(retvaltst)";
var_dump($errorTab);
var_dump($conn);
die ();
}
$parameter = array('tstchar' => $tstchar);
$parmOut = array('retval' => 'retval');
$ret = i5_program_call($prog, $parameter,$parmOut);
if (!$ret) {
throw_error("i5_program_call failed");
exit();
}
// close the program call
i5_program_close($prog);
echo($retval);
return;
}

The function takes a character which is passed to the page as part of the call and sends it to the service program function. The set up of the program prepare shows how we set the retval parameter as a “I5_RETVAL | I5_OUT” type, Easycom knows that this is where the return value from the function is to be set. The function call only accepts a character so the i5_program_prepare() understands that the “I5_RETVAL | I5_OUT” is only used to carry the result and will not be passed into the function. We have to set the $parmOut variable to reflect the retval content as this is where it will be stored by the program. Then we call the program function and echo out the result.

Here are a couple of test results.

http://www.phptst.shield.local/retvaltst.php?tstchar=D = 4
http://www.phptst.shield.local/retvaltst.php?tstchar=F = 6
http://www.phptst.shield.local/retvaltst.php?tstchar=Z = 11 (remember we only have 10 characters so anything above 10 says we did not find it! Its case sensitive as well)

So we can now reduce the coding effort and utilize the same functions in many cases whether they are called from ILEC or PHP.. Now we need to look at the possibility of picking up other return values such as strings and structures…. There are a number of other undocumented features which we will try out in later test, particularly the ability to pass by Value or by Reference.

Happy PHP’ing.

Chris…

Nov 23

A Bit more fun with the File display and update web pages.

OK so we thought we would have a bit more fun with the previous programs which allowed you to move around and update file records. This is very basic code so don’t expect too much, but as we always say it’s free and you can do what you want with it (or not!). We were thinking about what we could add to make it a bit more of a complete project, so we decided to add a few more pages and functions which would allow you to drill down to the data you were interested in updating. REMEMBER this code took us about 1 hour to create so it is not anything like production ready, but it does show some of the power you can use to add new interfaces over your existing programs and products using PHP and the i5_toolkit().

The first thing we did was moved the process back a few steps so we could let the user sign on and then pick the library they were interested in and then displaying the files it contained.

This is the index.php file which is the start of the process. Very simple and all it does is force you to pass in some credentials (sign on info) which it uses to get a list of the libraries on the target system. This is the code which generates the first page.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/
// start the session to allow session variables to be stored and addressed
session_start();
require_once("scripts/functions.php");
// get the previous return URL
$ret_url = $_SESSION['ret_url'];
// store this page as return URL
$_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
if(!isset($_SESSION['server'])) {
load_config();
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Display File Content</title>
<!-- add the main CSS -->

</head>

<body>
<?php
// if valid user is set connect using Private connection
if(isset($_SESSION['valid_usr'])) {
$conn = 0;
if(!connect($conn)) {
if(isset($_SESSION['Err_Msg'])) {
echo($_SESSION['Err_Msg']);
$_SESSION['Err_Msg'] = "";
}
echo("Failed to connect");
}
else {
Dsp_Lib_Obj($conn);
}
}
else {?>
<form name=login method="post" action="scripts/login.php">
<table width="20%" align="center" border="1" cellpadding="1">
<tr><td><label>User ID :</label></td><td><input type="text" name="usr" /></td></tr>
<tr><td>Password:</td><td><input type="password" name="pwd" /></td></tr>
<tr><td colspan="2"><?php if(isset($_SESSION['Err_Msg'])) { echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = ''; } ?></td></tr>
<tr><td><label>Connection type</label></td><td><select id="conn_type" name="conn_type"><option value="decrypt" selected="selected">Decrypted</option><option value="encrypt">Encrypted</option></select></td></tr>
<tr><td colspan="2" align=center><input type="submit" value="Log in" /></td></tr><?php
if(isset($_SESSION['Pwd_Err'])) {
if($_SESSION['Pwd_Err'] == 1) { ?>
<tr><td colspan="2" align=center>Sorry the credentials were rejected by the <?php echo($_SESSION['sys']); ?> System</td></tr><?php
$_SESSION['Pwd_Err'] = 0;
}
} ?>
</table>
</form><?php
} ?>
</body>
</html>

The config file is loaded at the beginning of the process which contains the target server to use. The function which does that is contained in the functions.php file which is shown next. this also contains all of the functions used to connect to the server and display the content for other pages. (I hope I cut and pasted everything we used). I did not copy the config file but you should be able to work out the layout and content based on the load_config() function.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

function connect(&$conn) {
// reset the the ErrMsg variable
unset($_SESSION['ErrMsg']);
// connect to the i5
$conId = 0;
if (isset($_SESSION['ConnectionID'])) {
$conId = $_SESSION['ConnectionID'];
}
$server = $_SESSION['server'];
$addlibl = array($_SESSION['install_lib']);
// options array for the private connection
$options = array(
I5_OPTIONS_PRIVATE_CONNECTION => $conId,
I5_OPTIONS_IDLE_TIMEOUT => $_SESSION['timeout'],
I5_OPTIONS_JOBNAME => 'PHPTSTSVR');
// connect to the system
if($_SESSION['conn_type'] == 'encrypt')
$conn = i5_pconnect($server,$_SESSION['usr'],d_pwd($_SESSION['pwd']),$options);
else
$conn = i5_pconnect($server,$_SESSION['usr'],$_SESSION['pwd'],$options);
// if connect failed
if(is_bool($conn) && $conn == FALSE) {
$errorTab = i5_error();
if ($errorTab['cat'] == 9 && $errorTab['num'] == 285){
$_SESSION['ConnectionID'] = 0;
$_SESSION['Err_Msg'] = "Failed to connect";
return -1;
}
else {
//set the error message
$_SESSION['Err_Msg'] = "Connection Failed " .i5_errormsg();
// send back to the sign on screen
$_SESSION['ConnectionID'] = 0;
return -1;
}
}
else {
// add the library list
add_to_librarylist($addlibl,$conn);
$ret = i5_get_property(I5_PRIVATE_CONNECTION, $conn);
if(is_bool($ret) && $ret == FALSE) {
$_SESSION['Err_Msg'] = "Failed to retrieve connection ID " .i5_errormsg();
$_SESSION['ConnectionID'] = 0;
return -1;
}
else {
$_SESSION['ConnectionID'] = $ret;
}
}
return 1;
}

/*
* Function to add the install library to the library list
* @parms
* libraries to add to the library list
* connection to use
*/

function add_to_librarylist($libraries,&$conn) {
$curlibl = "";
// get current librarylist
if (!i5_command("rtvjoba",array(),array("usrlibl"=>"curlibl"),$conn))
die("Could not retrieve current librarylist:" . i5_errormsg($conn));
// add our libraries to the librarylist
$curlibl .= " " .implode(" ",$libraries);
// if library list already set a CPF2184 message will be generated, treat as warning and by pass.
if(!i5_command("chglibl",array("libl"=>$curlibl),array(),$conn)) {
if(i5_errormsg($conn) != 'CPF2184')
echo("Could not change current librarylist:" . i5_errormsg($conn));
}
return 1;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Recs($conn,$file,$flib) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data $_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("
// the assoc array contains the field names so we can use those as the headers
foreach($rec as $key => $value) {
echo(" }
echo(" // now just write out the data again and get the next record to the end
do {
echo(" foreach($rec as $key => $value) {
echo(" }
echo(" } while($rec = i5_fetch_assoc($result));
echo(" i5_free_query($result);
return;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) = '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table><form name=updent method=post action=scripts/update_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$rec['RRN'] ." >");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=" .$key ." id=" .$key ." value='" .$value ."'");
if($key == "RRN")
echo(" readonly=readonly");
echo(" /></td></tr>");
$i++;
}
echo("<tr><td colspan=2><input type=submit value=Update /></td</tr></form></table>");
// button to get the next record
echo("<table><tr><td>");
if($id > 0) {
$id--;
echo("<input type=button value=PREV OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td><td>");
$id++;
}
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td></tr></table>");
echo("<input type=button value='Log Out' OnClick=location='scripts/logout.php' ?>");
// break the reference to the last element as per the manual
unset($value);
i5_free_query($result);
return;
}

/*
* function load_config()
* @parms
* *NONE
*/

function load_config() {
$config_file = "scripts/config.conf";
$comment = "#";
// open the config file
$fp = fopen($config_file, "r");
if(!$fp) {
echo("Failed to open file");
return 0;
}
// loop through the file lines and pull out variables
while(!feof($fp)) {
$line = trim(fgets($fp));
if ($line && $line[0] != $comment) {
$pieces = explode("=", $line);
$option = trim($pieces[0]);
$value = trim($pieces[1]);
$config_values[$option] = $value;
}
}
fclose($fp);
$_SESSION['server'] = $config_values['server'];
$_SESSION['install_lib'] = $config_values['install_lib'];
$_SESSION['timeout'] = $config_values['timeout'];
$_SESSION['refresh'] = $config_values['refresh'];
$_SESSION['logo'] = $config_values['company_logo'];
$_SESSION['dft_logo'] = $config_values['default_logo'];
$_SESSION['co_name'] = $config_values['company_name'];
if(isset($config_value['valid_usr'])) {
if($config_values['valid_usr'] != '') {
if(($config_values['usr'] != '') && ($config_values['pwd'] != '')) {
$_SESSION['valid_usr'] = $config_values['valid_usr'];
$_SESSION['usr'] = $config_values['usr'];
$_SESSION['pwd'] = $config_values['pwd'];
$_SESSION['auto_signon'] = 1;
}
}
}
return 1;
}

/*
* function to retrieve a list of libraries
*/

function Dsp_Lib_Obj(&$conn) {
$HdlList = i5_objects_list('QSYS', '*ALL', '*LIB',$conn);
if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
echo("

    while($list = i5_objects_list_read($HdlList)){
    echo(" if($list['DESCRIP'])
    echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
    else
    echo("-");
    echo(" echo(" }
    echo(" $ret = i5_objects_list_close($HdlList);
    if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
    return;
    }

    /*
    * function to retrieve a list of Files in a Library
    */

    function Dsp_Pf_Obj(&$conn,$lib) {
    $HdlList = i5_objects_list($lib, '*ALL', '*FILE',$conn);
    if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
    echo("

      while($list = i5_objects_list_read($HdlList)){
      // check if a PF-DTA
      if($list['EXT_ATTR'] == "PF") {
      echo(" if($list['DESCRIP'])
      echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
      else
      echo("-");
      echo(" echo(" }
      }
      $ret = i5_objects_list_close($HdlList);
      if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
      return;
      }
      ?>

      The next page called provides a list of the *FILE objects in the library which have a EXT_ATTR of "PF", this allow you to select a file and display all of the records from that file. Again all we do is use the i5_toolkit function i5_list_objects() to provide the list of objects in the library and then filter based on the extended attribute.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>List Files in Library</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pf_Obj($conn,$_REQUEST['lib']); ?>
      </body>
      </html>

      From this list you will display all of the records in the file, we only used a small file to test so the amount of data coming back was not important, if this was production and you had files with millions of records in them it could blow up the page as it would be too big! If you want to add something consider paging the file in sets of records or passing in a range of records at the start so only a limited number of records are retrieved and displayed. We have written plenty of posts and forums entries about how to achieve this, Google should bring up enough information to allow you to do it. This is the code for the page to display all of the records, it simply displays them and has a button which allows you to edit specific records. We just left the display for this as it was so once you start to edit you will have the ability to move around the records as previously.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>Edit File Content</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pfm_2($conn,$_REQUEST['file'],$_REQUEST['flib'],$_REQUEST['id']); ?>
      </body>
      </html>

      An that is it, took us about 1 hour to complete the set of functions and pages (lots of cut and paste). I thought I would add a few pictures to show you just what it produces. Remember this uses the Aura tollkit functions so they must be available for it to work. Again we did not add any fancy page sizing CSS etc so the images are very basic.

      This is the inital page which requires the user to pass in valid credentials to connect to the IBMi.

      Uses the sign in information to access the IBMi

      Initial signon screen

      Once you have signed in you should see a list of the libraries available on the system. You could provide filters or use the IBMi security to only allow the user to display the available libraries.

      List of Libraries

      The list of libraries from the system

      When you select the library you will be provided with a list of Physical Files.

      List of files in library

      A list of files in the library for selection

      Select the option to display the records and the following page is displayed.

      Records contained in the file

      A list of the records contained in a file select to edit

      Select a record to start editing from and the following page is displayed we moved the record up one to show the movement buttons.

      Edit a file

      Editing records in a file

      That is it, plenty of improvements I can think of just as I write this post but you get the idea. IBMi is the perfect hub for delivering data via web pages, we prefer to keep the web page generation off the IBMi but you have the option to use the iAMP server or Zend Server etc. to provide the same functionality. Stop thinking about how to move off the platform and start thinking about how you can harvest all of the good business logic and data you have built into the system.

      Hope you find this useful, for us its just a bit of fun and provides us with the ability to show what we can do with PHP. You should take up the challenge and start your investment in PHP now. Call us if you need more information or help.

      Happy PHP'ing..

      Chris...

Nov 23

Interesting discussion about XMLSERVICE and big data

I sometimes worry about how we perceive the Open Source products and what we as developers should expect from it. I like to keep a watch on what is happening within the IBMi/PHP eco system so I tend to watch the various forums looking at what people are doing. I had not been following the Zend forums for sometime as I was told that my opinions were not welcome, but had a bit of time to spare so took a quick look at what is going on. I came across the following post which raised a couple of questions Working with multiple occurrence data structures.

This seems to be the source of another post, Toolkit errors after update where at the bottom of the thread is a comment which had the following statement.

Frankly, I find your whole rant tiresome, but very well …

You are completely missing the point here Timo. Unlike some other toolkits, XMLSERVICE does NOT REQUIRE proprietary software “connection”, therefore you can use all manner of 1-tier (IBM i-2-IBM i) and 2-tier (any-2-IBM i) connection transports. PHP Toolkit / XMLSERVICE cannot control the behaviour of each and every connection possible, and in fact, there are 2-tier connections that don’t have any idea what a LIBL would be because this is a truly unique feature of IBM i. IN ANY EVENT, you can simply call CHGLIBL in any staeless or staefull XMLSERVICE job and change the state of the LIBL.

I read through the whole thread looking for the OP’s rant? I could not find it so I went through the previous post from the same OP which is where I found the possible source of the irritation. Basically the OP’s had mentioned that they did not have the performance issues when they ran with the Easycom Toolkit from Aura, I did not think it was said in a bad way, but simply that they saw better performance from the Aura toolkit than they were seeing with the new XMLSERVICE despite many improvements. That is not the point of this post as we have already said in many previous posts what our feelings are about the performance, instead I would like to mention a few things I would take away from this.

1. This is open source and as such if you have any problems with the way it runs you should be willing to pitch in and develop it to meet your needs. Alan and Roger have done a great job so far.
2. Don’t blame the test data, the problem is in the XMLSERVICE technology not the data or the amount generated. We all see applications we feel could be better designed and developed.
3. Comparison should be expected from clients, they took a decision based on the information they were given. Zend/IBM have said the XMLSERVICE is the way forward for PHP on IBMi we don’t but oh well! :-).
4. What is the cost of the effort so far in making the migration? Would it not have been more cost effective to stick with the original toolkit and paid for Aura to work it out?
5. Emotional responses should be avoided, I did not see any significant reason from the OP to justify the responses. But its free so don’t expect anything else.

Open source is a great option as long as you have the ability to change it to meet your requirements. Before you charge into a project which uses open source technology make sure it will meet all of your requirements before making it the standard, or be prepared to spend a lot of time adjusting the code to meet your needs. Sometimes paying for someone else to maintain and develop new features for the technology is much more cost effective than doing it yourself. Aura may seem to be forcing your hand with the original i5_toolkit functions by requiring you to pay for it, but if you look at the technology and what it offers it’s a very small price to pay for the benefits it brings. Plus you can always ask for improvements under the maintenance agreement which Aura would develop for free if they deemed it worthy.

We are still working with Aura and offer support and licensing for their products here in North America. If you need help in licensing the product or would like to know more about how we have implemented the PHP technology in our products let us know, we are very happy to help guide you to the light :-)

Happy PHP’ing

Chris…