Device Manager Update and monitoring

As mentioned earlier, DeviceManager is a software layer that is used to set tasks for devices and obtain results of their execution. Today we will talk about organizing the process of updating the program and monitoring jobs, since without this it is problematic to develop a product and implement its support.

Automation of the update process

Thoughts of automating the update process appeared almost immediately after the appearance of the product, since updating manually more than 100 jobs is laborious, and each of them is configured differently depending on its purpose. In practice, places were updated as new plugins were introduced in DeviceManager.

Providing support is even more time-consuming: spending several hours on a problem could reveal that it was “fixed” in earlier versions, and they simply forgot to update this workstation.

Providing DeviceManager with update functionality was risky: in the event of an update failure, the workstation completely failed. And a reliable mechanism was needed, like a Kalashnikov assault rifle, which helped in solving many problems, but did not create them.

We decided to apply the approach using additional lightweight software – Launcher.

Launcher must meet several requirements:

  • small volume;
  • low resource consumption;
  • high resistance to failures;
  • flexibility.

By flexibility is meant the ability to update not only DeviceManager or other programs, but primarily itself. Thus, we need to perform only the initial installation of the program on the workplace, then updates and work will occur autonomously.

Organization of data exchange

But flexibility cannot be achieved without a managerial staff who will manage updates, provide monitoring and necessary information. This link will be MIS (Medical Information System).

A lot of work has been done to support this concept on the part of IIAs, and this deserves a separate article. I will just give a brief overview:

The update begins by loading the new distribution kit into the MIS into a special directory with the version number. It is possible to mark the version as stable. Then it will automatically update at all workplaces.

For configs, a guide for configuring distributions was added, which stores templates of various plug-ins and devices.

To configure jobs, a work node log was started. Each workplace can be customized: specify a list of programs to install, their versions and configs.

To monitor the current state, a request is provided in Launcher – based on the response from which the indicator is displayed in graphical form:

  • Green – everything is normal;
  • Yellow — update required;
  • Red – not started;
  • Gray – not installed.

If additional information is required, a command from MIS to download logs is provided. In response, Launcher collects all the log files for network services from the work node, archives and sends it to the MIS. Launcher uses log rotation when working, which allows you to control the number of log files and the volume occupied by them.

The update takes place according to a schedule, at a strictly set time, which is set in the IIA. But if necessary, using the appropriate button, you can perform an emergency update of the selected workplace.

This toolkit is enough to automate the update, monitor the process and adjust it if something happens.

Development process

Through the use of the Qt framework, it was possible to reduce the number of external dependencies to one – the libarchive library, which is necessary for working with archives of various extensions.

This is due to the fact that distributions are downloaded to the MIS as a zip archive and during the update process this archive needs to be unpacked. And when creating logs for sending to an IIA request, it is necessary to perform compression to reduce the amount of data.

To use the code examples below, you must include the following header files: #include and #include .

Sample code for unzipping:

bool unpackingArchive(const QString &nameArchive, const QString &pathToUnpacking)
{
  archive *a;
  archive_entry *entry;
  int r;
  a = archive_read_new();
  archive_read_support_compression_all(a);
  archive_read_support_format_all(a);
  r = archive_read_open_filename(a, nameArchive.toStdString().c_str(),1024);
  if (r != ARCHIVE_OK)
 {
   QFile::remove(nameArchive);
   return false;
 }
 QDir().mkpath(pathToUnpacking);
 while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
 {
   __LA_MODE_T filetype = archive_entry_filetype(entry);
   if (filetype == AE_IFREG)
   {
     int64_t entry_size;
     const QString currentFile = archive_entry_pathname(entry);
     if (currentFile.contains("/"))
       QDir().mkpath(pathToUnpacking + "/" + currentFile.left(currentFile.lastIndexOf("/")));
     entry_size = archive_entry_size(entry);
     QByteArray fileContents;
     fileContents.resize(static_cast(entry_size));
     archive_read_data(a, fileContents.data(), static_cast(entry_size));
     const QString nameFile = pathToUnpacking + "/" + currentFile;
     QFile file(nameFile);
     if (!file.open(QFile::WriteOnly))
     {
       QString errorMessage = "Error open file " +
           QFileInfo(file).absoluteFilePath() + "; error string - " + file.errorString();
       qCritical() << errorMessage;
       return false;
     }
     file.write(fileContents,entry_size);
     file.close();
   }
 }
 archive_read_close(a);
 return true;
}

Example code for archiving:

bool packingZipArchive(const QString &pathToFolder, const QString &nameArchive)
{
 QDir dir(pathToFolder);
 QFileInfoList zipedList = dir.entryInfoList(QDir::Files);
 zipedList += dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);

 archive *zip = archive_write_new();
 archive_write_set_format_zip(zip);
 if (archive_write_open_filename(zip, nameArchive.toStdString().c_str()) != ARCHIVE_OK)
 {
   qCritical() << "Open zip file error: " << archive_error_string(zip);
   return false;
 }

 archive_entry *entry = archive_entry_new();
 for (const QFileInfo &i : zipedList)
 {
   writeFile(entry, i, zip, dir.absolutePath());
 }
 archive_entry_free(entry);

 archive_write_close(zip);
 archive_write_free(zip);
 return true;
}

void writeFile(archive_entry *entry, const QFileInfo &info, archive *arch, const QString &archiveRootDir)
{
 if (info.isDir())
 {
   QFileInfoList condition;
   condition << QDir(info.absoluteFilePath()).entryInfoList(QDir::Files) << QDir(info.absoluteFilePath()).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
   for (const QFileInfo &i :condition)
     writeFile(entry, i, arch, archiveRootDir);
 }
 else
 {
   struct stat st;
   QFile file(info.absoluteFilePath());
   if (!file.open(QFile::ReadOnly))
   {
     qCritical() << "File " << info.absoluteFilePath() << " not open: " << file.errorString();
     return;
   }
   qDebug() << "Add file " << info.absoluteFilePath();
   if (fstat(file.handle(), &st) == -1)
   {
     qWarning() << "Error read metadata from file " << info.absoluteFilePath();
   }
   else
     archive_entry_copy_stat(entry,&st);
   QString subPath = info.absoluteFilePath().remove(archiveRootDir);
   QRegExp regex("^(./|/)");
   if (subPath.indexOf(regex) > -1)
     subPath = subPath.remove(regex);
   archive_entry_set_pathname(entry, subPath.toStdString().c_str());
   archive_entry_set_size(entry, info.size());
   archive_entry_set_filetype(entry, AE_IFREG);
   if (archive_write_header(arch, entry) != ARCHIVE_OK)
   {
     qCritical() << "Open zip file error: " << archive_error_string(arch);
   }
   else
   {
     QByteArray data = file.readAll();
     archive_write_data(arch, data.data(), static_cast(data.size()));
   }
    archive_entry_clear(entry);
  }
}

Launcher update process

In the end, I would like to talk about the procedure for changing the version of Launcher itself. The fact is that using Launcher in the automation process is not a completely reliable method. When updating, a failure can also occur, this will not lead to a lightning-fast failure of the workplace, but it will bring a lot of trouble to restore.

To avoid this, it was decided to update as follows:

Launcher Launcher checks the availability of new versions in the IIA. If there is an update, the new distribution package is downloaded, unpacked and configured. Then, the current version launches the updated instance of the program without completing its work. After starting, the updated version searches for a previously launched instance and, if it finds it, closes it. At the end of the update process, all old files of the Launcher version launched at the beginning are deleted. This achieves a “seamless” update, the risk of failure at which is extremely small.

Total

Thanks to the automation of the update process, the job support procedure has been greatly simplified. When updating, the system administrator no longer needs access to the places themselves, they only need the MIS and the pre-configured Launcher, which takes all the work of updating, configuring and running DeviceManager on the workplace.

P.S. Within the framework of this article, it was not possible to fully disclose the organization of the update work by the IIA, but we will correct this in the following publications. See you soon!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *