This tutorial describes how to develop a CIM Provider in C++. This tutorial shows how to implement a provider for the Linux time service, and is related to the tutorial Developer Primer to WBEM and CIM.
Contents |
At this point, we have successfully modeled our application, interpreted our model in Managed Object Format, and loaded our MOF file into the CIMOM. The CIMOM now knows that there is such a thing as a NovlDevServ_TimeService object and that it is related to a Novell_LinuxUnitaryComputerSystem. There is still a bit of work to be done, however, and that is where the CIM provider comes in.
Every provider needs a couple of basic things:
Getting started means first taking care of these things. It is pretty simple. A macro, provided to us by OpenWBEM, takes care of hooking us into the provider. The rest is straightforward C++. Here's our code so far:
namespace NovlDevServ_TimeService
{
class TimeServiceProvider
{
}; // end class TimeServiceProvider
} // end namespace NovlDevServ_TimeService
OW_PROVIDERFACTORY(NovlDevServ_TimeService::TimeServiceProvider, timeserviceprovider)
The OW_PROVIDERFACTORY macro hooks us into the CIMOM. We always put this at the very end of our file, indicating first the fully qualified class name (including the namespace), and then an identifier that will be the name of our provider.
The CIMOM supports two different types of methods: intrinsic methods and extrinsic methods. Intrinsic methods provide the implementation primarily of the CIM meta schema. They are defined by the CIM specification and are implemented in a set of abstract base classes that you must subclass. Extrinsic methods provide capability for extensions you make. Extrinsic methods are basically those that you defined as class methods in the MOF file.
In order to implement a provider, we must a) provide the implementation for specific intrinsic methods that pertain to our model, and b) implement any extrinsic methods that are extensions pertinent to our model. The intrinsic methods that we must implement are determined by the type of provider that we are going to implement.
There are several types of providers that we can create:
In our case, our provider must be an instance provider, because clients will want to ask the CIM system to retrieve time-related information, like the time zone, and we want to do this dynamically. Our provider must also be a method provider, because clients will also want to ask the CIM system to set the time-related configuration information to client-specified values, and this requires a method invocation. And finally, our provider must also be an association provider, because our model indicates multiple objects with associations between them, and we need to tell the CIMOM how to manage these relations. We are not offering any indications and don't plan to generate them, so we don't need to be an indication provider.
Now we know that the time service provider, which we are just starting to implement, is going to be both a Method provider and an Association provider. Association providers are also Instance providers. What this means is that our new C++ class must subclass both the Association provider class and the Method provider class. Each of these abstract base classes provide some pure virtual functions that we must override in our time service provider.
These pure virtual functions that we must implement in our child class are the intrinsic methods we spoke of earlier. Therefore, once we know what type of provider we need to implement, we know which intrinsic methods we need to implement by simply looking up and implementing the pure virtual functions that are defined in the abstract base classes for the provider types we are implementing.
From this understanding, we know what methods we need to implement in our class:
Here's what our class looks like so far:
class TimeServiceProvider : public CppAssociatorProviderIFC, public CppMethodProviderIFC
{
public:
TimeServiceProvider()
: CppAssociatorProviderIFC()
, CppMethodProviderIFC()
{
}
virtual ~TimeServiceProvider()
{
}
virtual void enumInstanceNames(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const String& className,
CIMObjectPathResultHandlerIFC& result,
const CIMClass& cimClass)
{
}
virtual void enumInstances(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const String& className,
CIMInstanceResultHandlerIFC& result,
ELocalOnlyFlag localOnly,
EDeepFlag deep,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList,
const CIMClass& requestedClass,
const CIMClass& cimClass)
{
}
virtual CIMInstance getInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& instanceName,
ELocalOnlyFlag localOnly,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList,
const CIMClass& cimClass)
{
// When fully implemented, we shouldn't ever get this far
OW_THROWCIM(CIMException::NOT_FOUND);
return CIMInstance(CIMNULL);
}
virtual CIMObjectPath createInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMInstance& cimInstance)
{
// When fully implemented, we shouldn't ever get this far
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
"Instance creation is not supported for this class type");
return CIMObjectPath(CIMNULL);
}
virtual void modifyInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMInstance& modifiedInstance,
const CIMInstance& previousInstance,
EIncludeQualifiersFlag includeQualifiers,
const StringArray* propertyList,
const CIMClass& cimClass)
{
}
virtual void deleteInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& cop)
{
}
virtual void associators(
const ProviderEnvironmentIFCRef& env,
CIMInstanceResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& assocClass,
const String& resultClass,
const String& role,
const String& resultRole,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList)
{
}
virtual void associatorNames(
const ProviderEnvironmentIFCRef& env,
CIMObjectPathResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& assocClass,
const String& resultClass,
const String& role,
const String& resultRole)
{
}
virtual void references(
const ProviderEnvironmentIFCRef& env,
CIMInstanceResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& resultClass,
const String& role,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList)
{
}
virtual void referenceNames(
const ProviderEnvironmentIFCRef& env,
CIMObjectPathResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& resultClass,
const String& role)
{
}
virtual CIMValue invokeMethod(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& path,
const String& methodName,
const CIMParamValueArray& in,
CIMParamValueArray& out)
{
// When fully implemented, we shouldn't ever get this far
OW_THROWCIMMSG(CIMException::FAILED,
Format("Provider does not support method %1",methodName).c_str());
return CIMValue(CIMNULL);
}
virtual void getInstanceProviderInfo(InstanceProviderInfo& info)
{
}
virtual void getMethodProviderInfo(MethodProviderInfo& info)
{
}
virtual void getAssociatorProviderInfo(AssociatorProviderInfo& info)
{
}
}; // end class TimeServiceProvider
This shows placeholders for all of the intrinsic methods that we need to implement. This includes the method invokeMethod(), through which we will support any extrinsic methods that we offer in our provider.
As we read through this, we note the inclusion of a lot of different CIM data types. We need to account for these also. At the top of our file, we need to include several CIM header files to define these data types:
#include <openwbem/OW_CppAssociatorProviderIFC.hpp> #include <openwbem/OW_CppMethodProviderIFC.hpp> #include <openwbem/OW_CIMClass.hpp> #include <openwbem/OW_CIMInstance.hpp> #include <openwbem/OW_CIMException.hpp> #include <openwbem/OW_CIMValue.hpp> #include <openwbem/OW_CIMProperty.hpp> #include <openwbem/OW_CIMParamValue.hpp> #include <openwbem/OW_CIMObjectPath.hpp> #include <openwbem/OW_CIMDateTime.hpp> #include <openwbem/OW_CIMOMHandleIFC.hpp> #include <openwbem/OW_ResultHandlerIFC.hpp> #include <openwbem/OW_CIMDataType.hpp> #include <openwbem/OW_CIMInstanceEnumeration.hpp> #include <openwbem/OW_Format.hpp> #include <openwbem/OW_Exec.hpp> #include <openwbem/OW_FileSystem.hpp> using namespace OpenWBEM; using namespace WBEMFlags; #include "Utils.hpp" #include "NTPConf.hpp" #include "ClockConf.hpp" #include <sys/time.h>
We've actually included all the information we know we will need for the finished provider. The amount we need in order to get this to compile now is everything down to using namespace WBEMFlags;.
Now we should have a provider that can compile. It doesn't do anything, but at this point the provider should at least build successfully and compile into a shared object file. Here's what a sample Makefile could look like:
# Makefile for time service provider. OWPREFIX = /usr OWINCDIR = $(OWPREFIX)/include OWLIBDIR = $(OWPREFIX)/lib SONAME = libtimeserviceprovider.so LIBNAME = libtimeserviceprovider.so.0.0.1 TARGET = $(SONAME) ifdef (DEBUG) DEBUGFLAGS = -g -DDEBUG else DEBUGFLAGS = endif DEFS = -D_REENTRANT LIBS = -lpthread -L$(OWLIBDIR) -lopenwbem INCLUDES = -I$(OWINCDIR) CXX = g++ CXXFLAGS = $(DEBUGFLAGS) $(INCLUDES) $(DEFS) -Werror -Wall -pedantic -fPIC SRCS = \ timeservice.cpp OBJS = $(addsuffix .o, $(basename $(SRCS))) .SUFFIXES : .o .cpp $(TARGET): $(LIBNAME) $(OBJS) @if test ! -e `pwd`/$(SONAME); then \ ln -s `pwd`/$(LIBNAME) `pwd`/$(SONAME); \ fi $(LIBNAME): $(OBJS) $(CXX) -shared -Wl,-soname,$(SONAME) -o $(LIBNAME) $(OBJS) .cpp.o: $(CXX) $(CXXFLAGS) -c $< -o $@ clean: @rm -f $(TARGET) &> /dev/null @rm -f $(LIBNAME) &> /dev/null @rm -f $(OBJS) &> /dev/null
If you have entered everything correctly, simply running make' at the command line should correctly generate the expected targets, namely the versioned shared object library - libtimeserviceprovider.so.0.0.1 - and the symbolic link to the shared object library name, not versioned - libtimeserviceprovider.so.
We can start filling out our provider methods now. The ProviderInfo methods are easy to begin with. The first one, getInstanceProviderInfo(), will provide to the CIMOM the names of classes for which this provider provides instances. This provider provides instances for six classes, three regular classes and three association classes, as indicated in our object model. The third, getAssociatorProviderInfo(), does the same as the instance provider method, except it only provides the names of association classes, of which we have three, the same three we mentioned in getInstanceProviderInfo().
The second method, getMethodProviderInfo(), provides the names of all classes for which this provider supports extrinsic method invocation, along with the names of those methods. In our model, we only have one class that supports extrinsic methods â the NovlDevServ_TimeService class, which supports the ManageSystemTime method inherited from the Novell_TimeService class as well as the startservice, stopservice, and requeststatechange methods that are inherited from CIM_Service.
Here's an example of an implementation of getInstanceProviderInfo():
virtual void getInstanceProviderInfo(InstanceProviderInfo& info)
{
// regular classes
info.addInstrumentedClass("NovlDevServ_TimeService");
info.addInstrumentedClass("NovlDevServ_RemoteTimeServicePort");
info.addInstrumentedClass("NovlDevServ_TimeZoneSettingData");
// associations
info.addInstrumentedClass("NovlDevServ_HostedTimeService");
info.addInstrumentedClass("NovlDevServ_TimeServiceAccessBySAP");
info.addInstrumentedClass("NovlDevServ_TimeServiceSettingData");
}
That's pretty much all this method needs to accomplish. It simply provides the names of all the supported classes to the InstanceProviderInfo object that is passed in.
One problem you might notice is the specification of the class names via literal strings. This will work just fine, but it is an error-prone development practice that we want to avoid. We are going to end up using these names a lot in our code, and if we ever change the name of one of the classes in our MOF file, we don't want to have to go searching through our code looking for every place we typed the name. This practice also invites subtle errors that come from typing the class name wrong in one place and correctly in another. We want to use the same value every time to avoid these errors.
Let's address this concern by defining constant String objects for all of our constant string values for the provider. We want the strings within the object to be non-modifiable from within the consumer code, i.e. the provider code. We will define these in the same file, within the namespace, but not within the class; this makes it easier for us to define the complete object and the value in one statement.
Here is the complete list of constant String objects for this provider. Notice that we are including a lot of strings that we haven't talked about yet, but we will need later:
// Time service provider classes
static const String& TimeServiceClassName("NovlDevServ_TimeService");
static const String& RemoteTimeServicePortClassName("NovlDevServ_RemoteTimeServicePort");
static const String& TimeZoneSettingDataClassName("NovlDevServ_TimeZoneSettingData");
// Time service provider association classes
static const String& HostedTimeServiceAssnName("NovlDevServ_HostedTimeService");
static const String& TimeServiceAccessBySAPAssnName("NovlDevServ_TimeServiceAccessBySAP");
static const String& TimeServiceSettingDataAssnName("NovlDevServ_TimeServiceSettingData");
// Time service provider extrinsic methods
static const String& ManageSystemTimeMethodName("ManageSystemTime");
static const String& StartServiceMethodName("startservice");
static const String& StopServiceMethodName("stopservice");
static const String& RequestStateChangeMethodName("requeststatechange");
// Other classes
static const String& UnitaryComputerSystemClassName("Novell_LinuxUnitaryComputerSystem");
// Class properties
static const String& SystemCreationClassNameProperty("SystemCreationClassName");
static const String& SystemNameProperty("SystemName");
static const String& CreationClassNameProperty("CreationClassName");
static const String& NameProperty("Name");
static const String& EnabledStateProperty("EnabledState");
static const String& RequestedStateProperty("RequestedState");
static const String& EnabledDefaultProperty("EnabledDefault");
static const String& TimeOfLastStateChangeProperty("TimeOfLastStateChange");
static const String& StartedProperty("Started");
static const String& InstallDateProperty("InstallDate");
static const String& OperationalStatusProperty("OperationalStatus");
static const String& StatusProperty("Status");
static const String& HealthStateProperty("HealthState");
static const String& StartModeProperty("StartMode");
static const String& AccessInfoProperty("AccessInfo");
static const String& InfoFormatProperty("InfoFormat");
static const String& PortProtocolProperty("PortProtocol");
static const String& PreferProperty("Prefer");
static const String& InstanceIDProperty("InstanceID");
static const String& TimeZoneProperty("TimeZone");
static const String& UTCHardwareClockProperty("UTCHardwareClock");
static const String& AntecedentProperty("Antecedent");
static const String& DependentProperty("Dependent");
static const String& ManagedElementProperty("ManagedElement");
static const String& SettingDataProperty("SettingData");
// Property Values
static const String& TimeServiceValue("TimeService");
static const String& OKValue("OK");
static const String& AutomaticValue("Automatic");
static const String& NovlDevServSuseTimezoneValue("NOVLDEVSERV:SUSE:TIMEZONE");
// RPM Tags
static const String& HostSystemRPMTag("sld-release");
static const String& XntpRPMTag("xntp");
// Miscellaneous
static const String& TimeZoneConfigPath("/usr/share/zoneinfo/");
static const String& TimeZoneConfigCommandBase("/usr/sbin/zic -l ");
static const String& GetRequestFlag("GetRequest");
static const String& TimeDataFlag("timedata");
static const String& SetHWClockCommand("/sbin/hwclock --systohc ");
static const String& UseUTCHardwareClockCommandFlag("-u");
static const String& UseLocalClockCommandFlag("--localtime");
Here are the implementations of the ProviderInfo methods, using our string constants:
virtual void getInstanceProviderInfo(InstanceProviderInfo& info)
{
// regular classes
info.addInstrumentedClass(TimeServiceClassName);
info.addInstrumentedClass(RemoteTimeServicePortClassName);
info.addInstrumentedClass(TimeZoneSettingDataClassName);
// associations
info.addInstrumentedClass(HostedTimeServiceAssnName);
info.addInstrumentedClass(TimeServiceAccessBySAPAssnName);
info.addInstrumentedClass(TimeServiceSettingDataAssnName);
}
virtual void getMethodProviderInfo(MethodProviderInfo& info)
{
StringArray methods;
// These are all the methods we support for the time service class.
methods.append(ManageSystemTimeMethodName);
methods.append(StartServiceMethodName);
methods.append(StopServiceMethodName);
methods.append(RequestStateChangeMethodName);
// Add the time service class and the methods we support.
MethodProviderInfo::ClassInfo ci(TimeServiceClassName,
StringArray(),
methods);
info.addInstrumentedClass(ci);
// We don't support methods on any other classes.
}
virtual void getAssociatorProviderInfo(AssociatorProviderInfo& info)
{
info.addInstrumentedClass(HostedTimeServiceAssnName);
info.addInstrumentedClass(TimeServiceAccessBySAPAssnName);
info.addInstrumentedClass(TimeServiceSettingDataAssnName);
}
Next we'll concentrate in implementing the functionality to make our provider an instance provider. We need to define a few terms before moving on.
In order to implement an instance provider, we need to fill in the following methods:
Let's start with the enumInstances method. We need to be able to provide instances of all six of the classes that we manage in our provider. It is pretty easy to stub this method out:
virtual void enumInstances(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const String& className,
CIMInstanceResultHandlerIFC& result,
ELocalOnlyFlag localOnly,
EDeepFlag deep,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList,
const CIMClass& requestedClass,
const CIMClass& cimClass)
{
if (className.equalsIgnoreCase(TimeServiceClassName))
{
}
else if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
}
else if (className.equalsIgnoreCase(TimeZoneSettingDataClassName))
{
}
else if (className.equalsIgnoreCase(HostedTimeServiceAssnName))
{
}
else if (className.equalsIgnoreCase(TimeServiceAccessBySAPAssnName))
{
}
else if (className.equalsIgnoreCase(TimeServiceSettingDataAssnName))
{
}
else
{
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
Format("Class %1 is not supported.",className).c_str());
}
}
All we are doing here is providing structure. We need to determine what kind of instance we are being asked to provide - for which class are we providing instances. We simply check all the class names we know of. If it doesn't match any of the classes we know how to handle, there has been a mistake and we say in the final else clause.
The tricky part is, how do we create an instance of a class? Here's one example of how we create the instance for the NovlDevServ_TimeService class:
if (className.equalsIgnoreCase(TimeServiceClassName))
{
// There is exactly one instance of this class in the system,
// so create it and return it.
CIMInstance ci = createTimeServiceInstance(env,ns,cimClass);
result.handle(ci.clone(localOnly,
includeQualifiers,
includeClassOrigin,
propertyList));
}
In order to simplify this method, we have implemented a helper method, createTimeServiceInstance(). We define this method in the protected interface of our TimeServiceProvider class, so it can be used by any child classes but not invoked directly. Here's what that method looks like:
CIMInstance createTimeServiceInstance( const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMClass& cimClass=CIMClass(CIMNULL))
{
CIMOMHandleIFCRef handle = env->getCIMOMHandle();
// We get the computer system object here because the creation class name
// of the computer object will be this class's system creation class name,
// and the name of the computer object will be this class's system name.
CIMInstance computerSystem = getComputerSystemObject(env,ns);
CIMClass cc = cimClass;
if (!cimClass)
cc = handle->getClass(ns,
TimeServiceClassName,
E_NOT_LOCAL_ONLY,
E_INCLUDE_QUALIFIERS,
E_INCLUDE_CLASS_ORIGIN);
CIMInstance ci = cc.newInstance();
// Fill in the key values.
ci.setProperty(SystemCreationClassNameProperty,
computerSystem.getPropertyValue(CreationClassNameProperty));
ci.setProperty(SystemNameProperty,
computerSystem.getPropertyValue(NameProperty));
ci.setProperty(CreationClassNameProperty,
CIMValue(TimeServiceClassName));
ci.setProperty(NameProperty,
CIMValue(TimeServiceValue));
// Most of the rest of these are filled in with default values.
ci.setProperty(EnabledStateProperty,CIMValue(UInt16(2)));
ci.setProperty(RequestedStateProperty,CIMValue(UInt16(12)));
ci.setProperty(EnabledDefaultProperty,CIMValue(UInt16(2)));
ci.setProperty(TimeOfLastStateChangeProperty,CIMValue(CIMDateTime()));
ci.setProperty(StartedProperty,CIMValue(true));
ci.setProperty(InstallDateProperty,CIMValue(getSystemInstallDate()));
UInt16Array arr;
arr.append(2);
ci.setProperty(OperationalStatusProperty,CIMValue(arr));
ci.setProperty(StatusProperty,CIMValue(OKValue));
ci.setProperty(HealthStateProperty,CIMValue(UInt16(5)));
ci.setProperty(StartModeProperty,CIMValue(AutomaticValue));
return ci;
}
This is a fair amount of code, but actually what we are doing is pretty straightforward. We basically ask the CIMOM for a new instance of a class with the name we provided. Then we go about filling in the properties of the class with the appropriate values. For this particular class, we needed to get a couple of properties from the Novell_LinuxUnitaryComputerSystem object that this class is associated with, so we also had to retrieve the instance of this class.
There are two other helper methods that we need to implement. These are pretty simple methods but we will be using them a lot throughout the provider. These are getComputerSystemObject() and getSystemInstallDate():
CIMInstance getComputerSystemObject( const ProviderEnvironmentIFCRef& env,
const String& ns )
{
// We could retrieve the instance one time and store it for future
// reference, but it is possible that the instance can be changed
// between references, so it is best to retrieve the instance
// every time we are asked for it.
CIMOMHandleIFCRef handle = env->getCIMOMHandle();
CIMInstanceEnumeration instEnum =
handle->enumInstancesE(ns,UnitaryComputerSystemClassName);
if (instEnum.numberOfElements() > 0)
return instEnum.nextElement();
// no elements? this is an error
OW_THROWCIMMSG(CIMException::NOT_FOUND,
Format("No instance of %1 found",
UnitaryComputerSystemClassName).c_str());
}
CIMDateTime getSystemInstallDate()
{
// This value should never change so we retrieve it once and
// store it for future invocations.
if (!_systemInstallDate)
_systemInstallDate = getRpmInstallDate(HostSystemRPMTag);
return _systemInstallDate;
}
As we look through the code, we notice a function called getRpmInstallDate(). This is a helper function that basically determines the system time when an RPM was installed via the rpm utility. This function is defined in my utility library, which I create from the Utils.hpp and Utils.cpp files. There will be a few other functions defined there that I'll point out as we go along. In this particular case, we are taking a stab at the system install date by asking when the main RPM was installed. I'm doing this on a SUSE Linux Desktop, so the RPM I want is sld-release. You will need to determine the correct RPM for your system.
We should also notice the difference in how these two methods are implemented. The system install date is a value that will never change unless the system is reimaged, so once we retrieve that value we can hold onto it for future reference. However, the computer system object could theoretically change in between invocations, so we need to retrieve the latest instance each time that function is called.
Of course, if we are going to define _systemInstallDate as a class variable, and if we are going to test before assigning it a value, we had better initialize it. Here's the changed constructor, with _systemInstallDate added to the member initialization list:
public:
TimeServiceProvider()
: CppAssociatorProviderIFC()
, CppMethodProviderIFC()
, _systemInstallDate(CIMNULL)
{
}
So now we have taken care of enumerating instances of the time service class. This is pretty much the same thing that we have to do for each of the classes, with one important consideration: For some classes, there will be more than one instance that we need to return. How do we know this? It is based upon our model, and specifically, the cardinality of the relationships of certain objects to other objects. For example, there can be more than one instance of NovlDevServ_RemoteTimeServicePort associated with a single NovlDevServ_TimeService instance. That means that, when enumInstances() is called, we need to find all of the remote time service port instances, and there may be more than one. This basically means that we will need a looping construct, and within the loop, we will create and clone CIMInstances repeatedly until we have covered all of the instances of the class. Here's an example:
else if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
// There can be zero or more instances of this class, so
// we get an array of all the servers and return new instances
// for each one.
CIMDateTime installDate = getNtpInstallDate();
if (installDate)
{
NTPServerArray svrArray;
if (NTPConf::getServers(svrArray) == 0)
{
for (NTPServerArray::size_type i=0; i<svrArray.size(); ++i)
{
CIMInstance ci =
createRemoteTimeServicePortInstance(svrArray[i],installDate,env,ns,cimClass);
result.handle(ci.clone(localOnly,includeQualifiers,includeClassOrigin,propertyList));
}
}
}
else
OW_THROWCIMMSG(CIMException::FAILED,"Cannot determine install date for NTP");
}
The method createRemoteTimeServicePortInstance() is very similar to createTimeServiceInstance() in it's purpose. You can look at the source code for details. The main thing to learn from this example is how we construct a loop to enumerate all the instances of this particular class.
Let's move on to the enumInstanceNames() method. This method is very similar to enumInstances() but returns the object path or paths for each instance instead of the instances themselves. We can simply create a new method to handle the creation of the object path for each class we support. Like with the instance creation methods we used for enumInstances(), we will be needing these object path creation methods throughout the provider.
Creating the object path for a class isn't as hard as it might seem. Here's an example for the NovlDevServ_TimeService class:
CIMObjectPath createTimeServiceObjectPath( const ProviderEnvironmentIFCRef& env,
const String& ns )
{
// get the computer system instance - required for two key values
CIMInstance computerSystem = getComputerSystemObject(env,ns);
CIMObjectPath cop(TimeServiceClassName,ns);
cop.setKeyValue(SystemCreationClassNameProperty,
computerSystem.getPropertyValue(CreationClassNameProperty));
cop.setKeyValue(SystemNameProperty,
computerSystem.getPropertyValue(NameProperty));
cop.setKeyValue(CreationClassNameProperty,
CIMValue(TimeServiceClassName));
cop.setKeyValue(NameProperty,
CIMValue(TimeServiceValue));
return cop;
}
All we really need to do to create the correct object path is to create an instance of CIMObjectPath, using the name of the class that pertains to the object path we are creating. Next, set the values for all of the keys of the object using the setKeyValue() method of CIMObjectPath. After that, we are finished.
Here are the rest of the object path creation methods:
CIMObjectPath createTimeZoneSettingDataObjectPath( const String& ns )
{
CIMObjectPath cop(TimeZoneSettingDataClassName,ns);
cop.setKeyValue(InstanceIDProperty,
CIMValue(NovlDevServSuseTimezoneValue));
return cop;
}
CIMObjectPath
createRemoteTimeServicePortObjectPath( const String& serverName,
const ProviderEnvironmentIFCRef& env,
const String& ns )
{
// get the computer system instance - required for two key values
CIMInstance computerSystem = getComputerSystemObject(env,ns);
CIMObjectPath cop(RemoteTimeServicePortClassName,ns);
cop.setKeyValue(SystemCreationClassNameProperty,
computerSystem.getPropertyValue(CreationClassNameProperty));
cop.setKeyValue(SystemNameProperty,
computerSystem.getPropertyValue(NameProperty));
cop.setKeyValue(CreationClassNameProperty,
CIMValue(RemoteTimeServicePortClassName));
cop.setKeyValue(NameProperty,
CIMValue(serverName));
return cop;
}
CIMObjectPath createHostedTimeServiceAssnObjectPath( const ProviderEnvironmentIFCRef& env,
const String& ns )
{
CIMObjectPath cop(HostedTimeServiceAssnName,ns);
cop.setKeyValue(AntecedentProperty,
CIMValue(CIMObjectPath(ns,getComputerSystemObject(env,ns))));
cop.setKeyValue(DependentProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
return cop;
}
CIMObjectPath createTimeServiceSettingDataAssnObjectPath( const ProviderEnvironmentIFCRef& env,
const String& ns )
{
CIMObjectPath cop(TimeServiceSettingDataAssnName,ns);
cop.setKeyValue(ManagedElementProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
cop.setKeyValue(SettingDataProperty,
CIMValue(createTimeZoneSettingDataObjectPath(ns)));
return cop;
}
Once we have these methods finished, implementing enumInstanceNames() itself is pretty easy. The only thing we have to be careful of is the classes for which there can be more than one instance name. Of course, these are the same ones we had to do this for in enumInstances(). Here's the completed method:
virtual void enumInstanceNames(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const String& className,
CIMObjectPathResultHandlerIFC& result,
const CIMClass& cimClass)
{
if (className.equalsIgnoreCase(TimeServiceClassName))
{
// There is exactly one instance of this class in the system,
// so create a corresponding object path and return it.
result.handle(createTimeServiceObjectPath(env,ns));
}
else if (className.equalsIgnoreCase(TimeZoneSettingDataClassName))
{
// There is exactly one instance of this class in the system,
// so create a corresponding object path and return it.
result.handle(createTimeZoneSettingDataObjectPath(ns));
}
else if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
// There can be zero or more instances of this class, so we get
// an array of all the servers and create object paths for each
// instance.
CIMDateTime installDate = getNtpInstallDate();
if (installDate)
{
NTPServerArray svrArray;
if (NTPConf::getServers(svrArray)==0)
{
for (NTPServerArray::size_type i=0; i<svrArray.size(); ++i)
{
result.handle(createRemoteTimeServicePortObjectPath(svrArray[i].serverName,env,ns));
}
}
}
}
else if (className.equalsIgnoreCase(HostedTimeServiceAssnName))
{
// There is exactly one instance of this class in the system,
// so create a corresponding object path and return it.
result.handle(createHostedTimeServiceAssnObjectPath(env,ns));
}
else if (className.equalsIgnoreCase(TimeServiceSettingDataAssnName))
{
// There is exactly one instance of this class in the system,
// so create a corresponding object path and return it.
result.handle(createTimeServiceSettingDataAssnObjectPath(env,ns));
}
else if (className.equalsIgnoreCase(TimeServiceAccessBySAPAssnName))
{
// There can be zero or more instances of this class, so we get
// an array of all the servers and create object paths for each
// association.
CIMDateTime installDate = getNtpInstallDate();
if (installDate)
{
CIMObjectPath cop(className,ns);
cop.setKeyValue(AntecedentProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
NTPServerArray svrArray;
if (NTPConf::getServers(svrArray)==0)
{
for (NTPServerArray::size_type i=0; i<svrArray.size(); ++i)
{
cop.setKeyValue(DependentProperty,
CIMValue(createRemoteTimeServicePortObjectPath(svrArray[i].serverName,env,ns)));
result.handle(cop);
}
}
}
}
else
{
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
Format("Class %1 is not supported.",className).c_str());
}
}
The next method we need to implement is getInstance(). A lot of the getInstance() method implementation will look very much like (in fact, exactly like) the enumInstances() implementation. Remember that, although the implementations look similar, the purpose of the methods is different. It would be a mistake to simply call into enumInstances() from getInstance() or vice versa, since the implementation of one may change in the future and then break the other one in doing so.
A good example of this is seen if we look at the implementation within getInstance() for the NovlDevServ_RemoteTimeServicePort class, shown below:
String className = cimClass.getName();
if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMDateTime installDate = getNtpInstallDate();
// Examine the name and server name properties to see which
// instance we are talking about.
if (!installDate)
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
"NTP Support not found - not installed?");
CIMValue cv = instanceName.getKeyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name is missing from provided object path.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name is missing from provided object path.");
// Get the matching server, construct and return the corresponding instance.
NTPServer svr;
if (NTPConf::getServer(serverName,svr) != 0 ||
svr.serverName.length() == 0)
OW_THROWCIMMSG(CIMException::NOT_FOUND,
Format("Server %1 not found in configuration",
serverName).c_str());
CIMInstance ci = createRemoteTimeServicePortInstance(svr,installDate,env,ns,cimClass);
return ci.clone(localOnly,includeQualifiers,includeClassOrigin,propertyList);
}
We can immediately see that, whereas in enumInstances() we would loop through and return all the known instances of the specified class, in this case we are retrieving the single instance that matches the criteria that were passed in to the method.
This is pretty much the flavor of the method for each class we support. Here is the complete method:
virtual CIMInstance getInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& instanceName,
ELocalOnlyFlag localOnly,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList,
const CIMClass& cimClass)
{
String className = cimClass.getName();
if (className.equalsIgnoreCase(TimeServiceClassName))
{
// There is exactly one instance of this class in the system,
// so all we have to do is create the instance and return it.
CIMInstance ci = createTimeServiceInstance(env,ns,cimClass);
return ci.clone(localOnly,includeQualifiers,includeClassOrigin,propertyList);
}
else if (className.equalsIgnoreCase(TimeZoneSettingDataClassName))
{
// There is exactly one instance of this class in the system,
// so all we have to do is create the instance and return it.
CIMInstance ci = createTimeZoneSettingDataInstance(env,ns,cimClass);
return ci.clone(localOnly,includeQualifiers,includeClassOrigin,propertyList);
}
else if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMDateTime installDate = getNtpInstallDate();
// Examine the name and server name properties to see which
// instance we are talking about.
if (!installDate)
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
"NTP Support not found - not installed?");
CIMValue cv = instanceName.getKeyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name is missing from provided object path.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name is missing from provided object path.");
// Get the matching server, construct and return the corresponding instance.
NTPServer svr;
if (NTPConf::getServer(serverName,svr) != 0 ||
svr.serverName.length() == 0)
OW_THROWCIMMSG(CIMException::NOT_FOUND,
Format("Server %1 not found in configuration",serverName).c_str());
CIMInstance ci = createRemoteTimeServicePortInstance(svr,installDate,env,ns,cimClass);
return ci.clone(localOnly,includeQualifiers,includeClassOrigin,propertyList);
}
else if (className.equalsIgnoreCase(HostedTimeServiceAssnName))
{
CIMObjectPath antecedent = getOPKey(AntecedentProperty,instanceName);
// Verify the parameters.
if (!antecedent)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Antecedent key must be specified.");
CIMObjectPath computerSystemObjectPath =
CIMObjectPath(ns,getComputerSystemObject(env,ns));
if (!antecedent.equals(computerSystemObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified antecedent not found in system.");
CIMObjectPath dependent = getOPKey(DependentProperty,instanceName);
if (!dependent)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Dependent key must be specified.");
CIMObjectPath timeServiceObjectPath = createTimeServiceObjectPath(env,ns);
if (!dependent.equals(timeServiceObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified dependent not found in system.");
// Construct and return the instance.
CIMInstance ci = cimClass.newInstance();
ci.setProperty(AntecedentProperty,
CIMValue(computerSystemObjectPath));
ci.setProperty(DependentProperty,
CIMValue(timeServiceObjectPath));
return ci.clone(E_NOT_LOCAL_ONLY,includeQualifiers,includeClassOrigin,propertyList);
}
else if (className.equalsIgnoreCase(TimeServiceSettingDataAssnName))
{
CIMObjectPath managedElementObjectPath = getOPKey(ManagedElementProperty,
instanceName);
// Verify the parameters.
if (!managedElementObjectPath)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"ManagedElement key must be specified.");
CIMObjectPath timeServiceObjectPath = createTimeServiceObjectPath(env,ns);
if (!managedElementObjectPath.equals(timeServiceObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified ManagedElement not found in system.");
CIMObjectPath settingDataObjectPath = getOPKey(SettingDataProperty,
instanceName);
if (!settingDataObjectPath)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"SettingData key must be specified.");
CIMObjectPath timeZoneSettingDataObjectPath = createTimeZoneSettingDataObjectPath(ns);
if (!settingDataObjectPath.equals(timeZoneSettingDataObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified SettingData not found in system.");
// Construct and return the instance.
CIMInstance ci = cimClass.newInstance();
ci.setProperty(ManagedElementProperty,
CIMValue(timeServiceObjectPath));
ci.setProperty(SettingDataProperty,
CIMValue(timeZoneSettingDataObjectPath));
return ci.clone(E_NOT_LOCAL_ONLY,includeQualifiers,includeClassOrigin,propertyList);
}
else if (className.equalsIgnoreCase(TimeServiceAccessBySAPAssnName))
{
CIMDateTime installDate = getNtpInstallDate();
if (!installDate)
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"NTP support not found - not installed?");
CIMObjectPath antecedent = getOPKey(AntecedentProperty,
instanceName);
// Verify the parameters.
if (!antecedent)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Antecedent key must be specified.");
CIMObjectPath timeServiceObjectPath = createTimeServiceObjectPath(env,ns);
if (!antecedent.equals(timeServiceObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified antecedent not found in system.");
CIMObjectPath dependent = getOPKey(DependentProperty,
instanceName);
if (!dependent)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Dependent key must be specified.");
CIMValue cv = dependent.getKeyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Dependent key is invalid.");
// Ensure that a server name was specified.
String serverName;
cv.get(serverName);
if (serverName.empty())
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Dependent key is invalid.");
// Locate that server name in the configuration file.
NTPServer svr;
if (NTPConf::getServer(serverName,svr) != 0)
OW_THROWCIMMSG(CIMException::FAILED,
"Failed reading /etc/ntp.conf file");
if (svr.serverName.empty())
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Server not found in /etc/ntp.conf file");
CIMObjectPath rtspObjectPath = createRemoteTimeServicePortObjectPath(serverName,env,ns);
if (!dependent.equals(rtspObjectPath))
OW_THROWCIMMSG(CIMException::NOT_FOUND,
"Specified dependent not found in system.");
// Construct and return the instance.
CIMInstance ci = cimClass.newInstance();
ci.setProperty(AntecedentProperty,
CIMValue(timeServiceObjectPath));
ci.setProperty(DependentProperty,
CIMValue(rtspObjectPath));
return ci.clone(E_NOT_LOCAL_ONLY,includeQualifiers,includeClassOrigin,propertyList);
}
else
{
// We shouldn't ever get this far
OW_THROWCIM(CIMException::NOT_FOUND);
return CIMInstance(CIMNULL);
}
}
Now we move on to createInstance(), modifyInstance(), and deleteInstance(). In our case, this isn't as much work as it seems. Here's what we have to consider for each instance:
Here's the createInstance() method:
virtual CIMObjectPath createInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMInstance& cimInstance)
{
String className = cimInstance.getClassName();
// We only support instance creation on one class.
if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMDateTime installDate = getNtpInstallDate();
if (!installDate)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"NTP support not found - not installed?");
// Make sure all required values are specified.
CIMValue cv = cimInstance.getPropertyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property must be specified.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property must be specified.");
Bool isPreferredTimeService = false;
cv = cimInstance.getPropertyValue(PreferProperty);
if (cv)
cv.get(isPreferredTimeService);
// Based on the provided information, update the system configuration.
if (NTPConf::setServer(serverName,bool(isPreferredTimeService))!=0)
OW_THROWCIMMSG(CIMException::FAILED,
Format("Attempt to create %1 instance failed.",
RemoteTimeServicePortClassName).c_str());
return createRemoteTimeServicePortObjectPath(serverName,env,ns);
}
else
{
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
Format("Instance creation is not supported for class %1",className).c_str());
return CIMObjectPath(CIMNULL);
}
}
Creating an instance of a remote time service port requires a server name and whether or not this service is designated as the preferred service. As we can see in the code, the caller must specify the server name or else we can't create a new instance. Indicating whether or not the service is preferred, however, is optional; we default to false if it isn't specified. Remember, each instance pertains to real data, so after this method completes, there will be a new entry in the /etc/ntp.conf file that matches the data provided to createInstance().
Here's the code for modifyInstance():
virtual void modifyInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMInstance& modifiedInstance,
const CIMInstance& previousInstance,
EIncludeQualifiersFlag includeQualifiers,
const StringArray* propertyList,
const CIMClass& cimClass)
{
String className = cimClass.getName();
// We only support modification of the time zone and remote time service instances.
if (className.equalsIgnoreCase(TimeZoneSettingDataClassName))
{
CIMInstance mci =
modifiedInstance.createModifiedInstance(previousInstance,
includeQualifiers,
propertyList,
cimClass);
CIMValue cv(CIMNULL);
// Save the time zone setting from the previous time zone,
// for recovery purposes.
String oldTimeZone;
cv = previousInstance.getPropertyValue(TimeZoneProperty);
if (cv)
cv.get(oldTimeZone);
else
OW_THROWCIMMSG(CIMException::FAILED,
"Logic error? Couldn't retrieve previous timezone value.");
// If they don't specify this value we default to true.
Bool useUTCHardwareClock = true;
cv = mci.getPropertyValue(UTCHardwareClockProperty);
if (cv)
cv.get(useUTCHardwareClock);
// This value is required and must be set by the client.
cv = mci.getPropertyValue(TimeZoneProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"TimeZone property must be specified.");
// Make sure the value provided is valid.
String newTimeZone;
cv.get(newTimeZone);
if (newTimeZone.empty())
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Invalid time zone specified.");
// Attempt to set the new time zone.
String timeZoneFile = TimeZoneConfigPath;
timeZoneFile += newTimeZone;
if (!FileSystem::canRead(timeZoneFile))
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Invalid time zone specified.");
String cmd = TimeZoneConfigCommandBase + newTimeZone;
if (Exec::safeSystem(cmd.tokenize())!=0)
{
// Attempt to set new time zone failed, try to restore old time zone.
cmd = TimeZoneConfigCommandBase + oldTimeZone;
Exec::safeSystem(cmd.tokenize());
OW_THROWCIMMSG(CIMException::FAILED,
Format("Failed to set time zone to %1",
newTimeZone).c_str());
}
// Attempt to set the preference on whether to use utc time.
if (ClockConf::modify(bool(useUTCHardwareClock),newTimeZone)!=0)
{
// If the attempt to set utc time preference fails,
// try to restore the old time zone.
cmd = TimeZoneConfigCommandBase + oldTimeZone;
Exec::safeSystem(cmd.tokenize());
OW_THROWCIMMSG(CIMException::FAILED,
"Failed to modify /etc/sysconfig/clock time");
}
}
else if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMDateTime installDate = getNtpInstallDate();
if (!installDate)
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
"Ntp support not found - not installed?");
CIMInstance mci =
modifiedInstance.createModifiedInstance(previousInstance,
includeQualifiers,
propertyList,
cimClass);
// Make sure the name property is specified.
CIMValue cv = mci.getPropertyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property must be specified.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property must be specified.");
// A value for "prefer" is not required, default to false
// if not specified.
Bool isPreferredTimeService = false;
cv = mci.getPropertyValue(PreferProperty);
if (cv)
cv.get(isPreferredTimeService);
// Update the NTP configuration.
if (NTPConf::setServer(serverName,bool(isPreferredTimeService))!=0)
OW_THROWCIMMSG(CIMException::FAILED,
"Failed to modify /etc/ntp.conf");
}
else
{
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
Format("Modification of instances of %1 is not supported",
className).c_str());
}
}
Notice how we use the previous instance to obtain values as they were set prior to the modifyInstance() call, in case we need to roll back the previous values. The modified instance contains all of the new values. We check these new values to make sure they are valid and that we have all the values we need, then try to apply the updates to the system.
Here's the deleteInstance() method:
virtual void deleteInstance(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& cop)
{
String className = cop.getClassName();
// Instance deletion is only supported for remote time service port.
if (className.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMDateTime installDate = getNtpInstallDate();
if (!installDate)
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
"Ntp support not found - not installed?");
// Make sure the server name is specified.
CIMValue cv = cop.getKeyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name parameter must be specified.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name parameter must be specified.");
// Delete the server from the NTP configuration.
if (NTPConf::deleteServer(serverName)!=0)
OW_THROWCIMMSG(CIMException::FAILED,
"Failed deleting instance from /etc/ntp.conf.");
}
else
{
OW_THROWCIMMSG(CIMException::NOT_SUPPORTED,
Format("Deletion of instances of %1 is not supported",
className).c_str());
}
}
Whew! That's a lot of work just to support an instance provider. Fortunately, implementing an instance provider is probably what requires the most work. We are pretty close to being finished now.
We're now at a point where we can load our provider into the CIMOM, and browse a bit with our preferred CIM browsing tool to see what we have done so far. The easiest way to continue developing your provider in your development directory while also having the provider loaded when the CIMOM starts up is to create a symbolic link. We can go into the directory that contains C++ providers, usually /usr/lib/openwbem/c++providers, and symbolically link to our library, libtimeserviceprovider.so. Then we can start up the CIMOM and it should load our provider.
Let's check our time service class first. Navigate through the hierarchy to find the NovlDevServ_TimeService class (it's under CIM_ManagedElement->CIM_ManagedSystemElement->CIM_LogicalElement->CIM_EnabledLogicalElement->CIM_Service->Novell_TimeService). Highlight the NovlDevServ_TimeService class. In the pane on the right of your SNIA browser, you should get a view that shows the properties that are defined for this class:
Figure 8: Locating the NovlDevServ_TimeService class in the SNIA CIM Browser
So far this information is only coming from the MOF file. However, now that we have implemented the instance provider interface, we should be able to do some more interesting things. Click on the Instances menu in the SNIA browser, then choose Show. This window should come up:
Figure 9: The Instance Editor for the SNIA CIM Browser
Still not too much of interest, but notice that there is an available instance indicated on the left. That instance on the left came from the enumInstanceNames() method we filled in. Highlight that instance and watch as the values are filled in:
Figure 10: The Instance Editor, with a highlighted instance and data values filled in
When we highlight that instance, the getInstance() method is called, and it returns the values that are filled in for the properties in the right view pane. We can go ahead and check all of the classes to see if they are also functioning like we expected.
Let's take a look at some of the other Instance Provider interface methods we implemented, such as createInstance(), modifyInstance(), and deleteInstance(). This is most easily demonstrated on a class that supports all three, so let's do this with the NovlDevServ_RemoteTimeServicePort class. Highlight that class, then bring up the Instance Editor by choosing the Show option from the Instances menu.
Once it comes up, let's highlight one of the instances and take note of the values defined for the properties. Here's what mine looks like:
Figure 11: The instance editor showing multiple available instances
If I open the file /etc/ntp.conf I can see the entry in that file that corresponds to this instance. It simply says:
server 137.65.1.100
Now let's create a new instance of NovlDevServ_RemoteTimeService port. From the Object menu, choose Create New Instance. It creates another instance entry, named New Instance, in the left panel, and in the right we can fill in values for the properties. The only property we have to specify is the Name property. We can make up any old thing for the sake of testing this out. I entered myntpserver.mydomain.com, and then hit Enter.
Once we have entered the value for Name, from the Object menu, choose Save Instance. You should see the name of the instance on the left panel change from New Instance to something like the others. In the right, you should see the name you entered as the value for Name as well as for AccessInfo. The system probably filled in default values for a lot of the other parameters.
Figure 12: The instance editor showing a newly added instance
Now if we look in the NTP configuration file, we should see the new entry. In mine, I see it at the bottom of the file after the other one:
server 137.65.1.100 server myntpserver.mydomain.com
Now we can try modifying an instance. First, let's change to a different instance. I'm going to choose the one named 137.65.1.100. Next, click in the value column for the Prefer property. Change the value from false to true and hit Enter. Then, from the Object menu, choose Save Instance. Select a different instance, then select the modified one again, to force a refresh of the values. Your Prefer option should now have the value of true.
Figure 13: The instance editor showing a modified instance
And if I look in my NTP configuration file, I see:
server 137.65.1.100 prefer server myntpserver.mydomain.com
Notice how the server I modified, 137.65.1.100, now has the prefer option set.
At this point, I can select the instance I just added, myntpserver.mydomain.com, and modify the Prefer option on that to say true. I do this just like I did for the other instance. When I save the instance, it updates the configuration file, removing the prefer option from 137.65.1.100, and adding that option to myntpserver.mydomain.com:
server 137.65.1.100 server myntpserver.mydomain.com prefer
Keep in mind that the provider itself is not doing the actual file modification. It is calling into a library that we linked into our provider, and the library is modifying the files. That library supports the addition of the prefer option, including removing it from a file and making sure there is only one preferred service.
The last thing to test is whether we can delete an instance. We know we can delete the instance for myntpserver.mydomain.com, since we are the ones that added it and we know it is bogus. Select the instance, then from the Object menu, choose Delete Instance. The instance disappears from the left hand pane:
Figure 14: The instance editor showing that the instance has been deleted
And if I look at my NTP configuration file, it is back to normal:
server 137.65.1.100
The NovlDevServ_TimeZoneSettingData class does not support creation or deletion, but it does support modification. We can toy around with modifying that class as well. As you do this, notice how the value in /etc/sysconfig/clock changes after each modification. Of course, again there is library code that is actually opening and editing the configuration file, but our provider is handling the transaction.
There are four methods that we need to implement in order to fill out the association provider interface. In order to explain what these methods do, let's establish a simple example.
In our example, let's say that class A and class B are associated to each other via class ABAssn. If this is the case, then below are outlined the responsibilities of the four different methods of the association provider interface:
Let's start by implementing the associators() method:
virtual void associators(
const ProviderEnvironmentIFCRef& env,
CIMInstanceResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& assocClass,
const String& resultClass,
const String& role,
const String& resultRole,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList)
{
String objectClassName = objectName.getClassName();
if (assocClass.equalsIgnoreCase(HostedTimeServiceAssnName))
{
if (objectClassName.equalsIgnoreCase(UnitaryComputerSystemClassName))
{
CIMInstance ci = createTimeServiceInstance(env,ns);
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
else if (objectClassName.equalsIgnoreCase(TimeServiceClassName))
{
CIMInstance ci = getComputerSystemObject(env,ns);
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
else if (assocClass.equalsIgnoreCase(TimeServiceAccessBySAPAssnName))
{
CIMDateTime installDate = getNtpInstallDate();
if (! installDate) return;
if (objectClassName.equalsIgnoreCase(TimeServiceClassName))
{
// There are potentially multiple instances on the other side
// of this association, but enumInstances() already knows
// how to provide this information to us.
CIMOMHandleIFCRef handle = env->getCIMOMHandle();
CIMClass cc = handle->getClass(ns,
RemoteTimeServicePortClassName,
E_NOT_LOCAL_ONLY,
E_INCLUDE_QUALIFIERS,
E_INCLUDE_CLASS_ORIGIN);
enumInstances(env,ns,RemoteTimeServicePortClassName,result,
E_NOT_LOCAL_ONLY,E_DEEP,includeQualifiers,
includeClassOrigin,propertyList,cc,cc);
}
else if (objectClassName.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMInstance ci = createTimeServiceInstance(env,ns);
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
else if (assocClass.equalsIgnoreCase(TimeServiceSettingDataAssnName))
{
if (objectClassName.equalsIgnoreCase(TimeServiceClassName))
{
CIMInstance ci = createTimeZoneSettingDataInstance(env,ns);
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
else if (objectClassName.equalsIgnoreCase(TimeZoneSettingDataClassName))
{
CIMInstance ci = createTimeServiceInstance(env,ns);
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
}
This is pretty straightforward. We check to see which of the three association classes we know about matches the one supplied. Once we know which association we are dealing with, we determine which reference we have been given. Then we go out and retrieve all instances of classes that are associated with that reference via the provided association class. Most of the time this is pretty easy to do. The exception is with the NovlDevServ_TimeServiceAccessBySAP association; since that association is a one-to-many relationship, if the reference class name passed in is for an instance of the time service class, there could be many related remote time service port instances, so we have to retrieve them all. Luckily for us, we already wrote the code to do that in enumInstances(), and it will do exactly what we want. We simply invoke it in that case.
Be careful with this particular technique. Simply calling enumInstances() will not always work or be the most effective way of solving the problem. This can especially be true for associations that represent many-to-many relationships. However, in this case it works fine.
The implementation for associatorNames() is almost exactly the same. Instead of returning instances, we return object paths. And instead of invoking enumInstances(), we invoke enumInstanceNames().
One issue we need to address is, how do I get an object path for the computer system object? We didn't ever write a method for creating the object path for the computer system.
The easy answer to this question is that we can easily construct an object path from an instance. So if we get the instance by calling getComputerSystemObject(), we can then construct a new object path from the instance:
CIMInstance ci = getComputerSystemObject(env,ns); result.handle(CIMObjectPath(ns,ci));
This code may not be the most efficient way to get an object path for the computer system, however. We're not going to get too worried about it, but it is important to know that there's more than one way to do things.
Now let's take a look at the implementation for references():
virtual void references(
const ProviderEnvironmentIFCRef& env,
CIMInstanceResultHandlerIFC& result,
const String& ns,
const CIMObjectPath& objectName,
const String& resultClass,
const String& role,
EIncludeQualifiersFlag includeQualifiers,
EIncludeClassOriginFlag includeClassOrigin,
const StringArray* propertyList)
{
CIMOMHandleIFCRef handle = env->getCIMOMHandle();
CIMClass cc = handle->getClass(ns,resultClass,
E_NOT_LOCAL_ONLY,
E_INCLUDE_QUALIFIERS,
E_INCLUDE_CLASS_ORIGIN,
NULL);
CIMInstance ci = cc.newInstance();
if (resultClass.equalsIgnoreCase(HostedTimeServiceAssnName))
{
ci.setProperty(AntecedentProperty,
CIMValue(CIMObjectPath(ns,getComputerSystemObject(env,ns))));
ci.setProperty(DependentProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
else if (resultClass.equalsIgnoreCase(TimeServiceAccessBySAPAssnName))
{
CIMDateTime installDate = getNtpInstallDate();
if (!installDate) return;
ci.setProperty(AntecedentProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
String objectClassName = objectName.getClassName();
if (objectClassName.equalsIgnoreCase(TimeServiceClassName))
{
NTPServerArray svrArray;
if (NTPConf::getServers(svrArray)==0)
{
for (NTPServerArray::size_type i=0; i<svrArray.size(); ++i)
{
ci.setProperty(DependentProperty,
CIMValue(createRemoteTimeServicePortObjectPath(svrArray[i].serverName,
env,ns)));
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
}
else if (objectClassName.equalsIgnoreCase(RemoteTimeServicePortClassName))
{
CIMValue cv = objectName.getKeyValue(NameProperty);
if (!cv)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property is required.");
String serverName;
cv.get(serverName);
if (serverName.length()==0)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
"Name property is required.");
NTPServer svr;
if (NTPConf::getServer(serverName,svr)!=0 ||
svr.serverName.length()==0)
return; // server doesn't exist
ci.setProperty(DependentProperty,CIMValue(objectName));
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
else if (resultClass.equalsIgnoreCase(TimeServiceSettingDataAssnName))
{
ci.setProperty(ManagedElementProperty,
CIMValue(createTimeServiceObjectPath(env,ns)));
ci.setProperty(SettingDataProperty,
CIMValue(createTimeZoneSettingDataObjectPath(ns)));
result.handle(ci.clone(E_NOT_LOCAL_ONLY,
includeQualifiers,
includeClassOrigin,
propertyList));
}
}
Again, pretty straightforward. We determine which resultClass we have been given - which association class it represents. Based on that, we fill in the references of the instance that we are going to return and then return the instance. Since two of our associations are one-to-one associations, it is pretty easy as we can see above. For the one association which is one to many, we clone the instance for each reference we have. As you'd expect, referenceNames() is pretty much the same, returning object paths instead of instances.
Now that we've implemented all of these methods, we should be able to use the SNIA browser to see how some of our classes are related to other classes. Navigate to the NovlDevServ_TimeZoneSettingData class in the browser and highlight it. Then from the Instances menu, choose Show. Within the instance editor, highlight an instance in the left pane, and then select the Related Objects tab. You should now see an object path, showing that this instance of NovlDevServ_TimeZoneSettingData is related to a NovlDevServ_TimeService object:
Figure 15: The instance editor showing the objects related to a time zone setting data instance
The associatorNames() method provided the object path that appears in the right hand pane.
If you click on the Associations tab, you should also see the object paths of any classes that are associators for this instance of NovlDevServ_TimeZoneSettingData:
Figure 16: The instance editor showing associations of a time zone setting data instance
The referenceNames() method provided the object path information that appears in the right hand pane.
Let's do the same thing, but with an instance of NovlDevServ_TimeService. If we click on the Related Objects tab, we will see not one, but four different related objects:
Figure 17: The instance editor showing the objects related to a time service instance
That's because, at least on my machine, my time service is related to four different objects. It has an association with a time zone setting, an association with the computer system, and one association each to two different remote time service ports. In the right hand pane the object paths for each of these instances are displayed.
We can expect the same thing if we look at the Associations tab. Even though we only defined three associations in our MOF file, we should see four associations in the right hand pane because there are four related objects, and therefore there must be a unique association instance to represent each relationship.
Figure 18: The instance editor showing associations of a time service instance
And, as expected, we can see all four associations in the right hand pane.
We're on the home stretch now! All we have remaining to do is to implement the method provider. That is just one method, the invokeMethod() method.
The purpose of invokeMethod() is to allow the CIMOM to do anything outside of the scope of what it knows how to do intrinsically. For example, the CIMOM knows how to modify an instance of a class and knows that the provider can take care of what modifying that instance means on the system itself. But not all classes can be started, or refreshed, or whatever. There are infinitely many things that a provider can do as long as we provide a method like invokeMethod() as a catch-all for doing things that we can't do any other way.
In our case, we are going to support one single method from the CIMOM, namely ManageSystemTime. A class that inherits from CIM_Service already advertises three methods - startservice, stopservice, and requeststatechange. ManageSystemTime comes from the Novell_TimeService class. Here, we are talking about the time service, not NTP, which is a different topic and a different provider, but the ability of the system to know what time it is. It doesn't really make any sense to start or stop the time service on a computer, but we do need to be able to manage the system time - in other words, change the time or date. So we will implement ManageSystemTime and indicate that the other methods are unsupported.
So far, we are just checking to see if the method passed in is ManageSystemTime, and returning the value for unsupported otherwise:
virtual CIMValue invokeMethod(
const ProviderEnvironmentIFCRef& env,
const String& ns,
const CIMObjectPath& path,
const String& methodName,
const CIMParamValueArray& in,
CIMParamValueArray& out)
{
if (methodName.equalsIgnoreCase(ManageSystemTimeMethodName))
{
}
else
{
// We don't support startservice, stopservice, or requeststatechange
// for the time service. Nor do we support anything else
// that may have been passed in.
// Return 1, which is the value for not supported.
return CIMValue(UInt32(1));
}
}
Next we need to determine whether we are retrieving or setting the system time. Remember, ManageSystemTime is a multi-functional method, where we can either set or retrieve the system time (refer to the MOF file).
if (methodName.equalsIgnoreCase(ManageSystemTimeMethodName))
{
Bool isGetRequest = true;
if (in.size())
{
// Make sure at least one input parameter is specified.
if (!(in[0].getName().equalsIgnoreCase(GetRequestFlag)))
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
Format("Input parameter '%1' must be specified",
GetRequestFlag).c_str());
in[0].getValue().get(isGetRequest);
}
if (isGetRequest)
{
}
else
{
}
}
If this is a get request only, we need to retrieve the system time, populate the return values array, and return success if everything goes okay. We first simply make a system call to get the current system time. Next we create a CIMDateTime instance and fill in the time and microseconds from the system time we obtained. Finally, we add it to the out parameter, checking to make sure if the array is not empty, that it contains the TimeData flag and that we add it there; otherwise, if the out array is empty, we simply append the TimeData flag along with the time we retrieved.
Here's the code:
if (isGetRequest)
{
// We are retrieving the system time. Get the system time from
// gettimeofday().
struct timeval tv;
if (::gettimeofday(&tv,NULL)!=0)
return CIMValue(UInt32(4));
// Break the timeval struct out into a CIMDateTime.
CIMDateTime timeData = CIMDateTime(DateTime(tv.tv_sec));
timeData.setMicroSeconds(tv.tv_usec);
timeData.setInterval(false);
if (out.size() > 0)
{
// If the out array already has something in it, make
// sure it is the TimeData property, and then populate
// that with the time information.
if (!(out[0].getName().equalsIgnoreCase(TimeDataFlag)))
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
Format("Invalid output parameter - Only '%1' allowed",
TimeDataFlag).c_str());
out[0].setValue(CIMValue(timeData));
}
else
// Otherwise, we just append the TimeData property and value.
out.append(CIMParamValue(TimeDataFlag,CIMValue(timeData)));
// We successfully got the time, so we can return from here.
return CIMValue(UInt32(0));
}
If the request is to set the system time, we need to take care of setting both the system time and whether this system uses UTC time or local time for the hardware clock time. First we check to see if we can get effective root permission. While the CIMOM does run as root, the threads it spawns retain the effective root permission but run as a different user to avoid security issues. Still, they should have effective root permission; otherwise it cannot make the kinds of changes it needs to make.
Once we know we have the right permission, we extract the time setting from the input parameter (the second one in the array). We use this information, seconds and microseconds, to set the system time, using the system settimeofday() command.
After we have done that, we simply reset the value for which time the hardware clock uses.
Here is the code:
else
{
// We are going to set the system time.
// Make sure we have permission to do this.
if (::geteuid()!=0)
OW_THROWCIMMSG(CIMException::ACCESS_DENIED,
"Insufficient permission to set system time.");
// We already verified the first parameter. We need to make
// sure that the time was also specified.
if (in.size() < 2 ||
in[1].getName().equalsIgnoreCase(TimeDataFlag)==false)
OW_THROWCIMMSG(CIMException::INVALID_PARAMETER,
Format("Input parameter '%1' must be specified",
TimeDataFlag).c_str());
// Convert the specified CIMDateTime object
// into a struct timeval, then set the system
// time using settimeofday().
CIMDateTime timeData;
in[1].getValue().get(timeData);
DateTime dt = timeData.toDateTime();
struct timeval tv;
tv.tv_sec = dt.get();
tv.tv_usec = dt.getMicrosecond();
if (::settimeofday(&tv,NULL)!=0)
return CIMValue(UInt32(4));
// Set whether we use the hardware clock.
String timeZone;
bool useUTCHardwareClock;
ClockConf::get(timeZone,useUTCHardwareClock);
String cmd = SetHWClockCommand;
cmd += (useUTCHardwareClock ?
UseUTCHardwareClockCommandFlag :
UseLocalClockCommandFlag);
if (Exec::safeSystem(cmd.tokenize())!=0)
// Failed to set the system hardware clock time
return CIMValue(UInt32(4));
// We successfully set the time, so we can return from here.
return CIMValue(UInt32(0));
}
}
That should be it. Let's test it out. First, bring up the Instance Editor for NovlDevServ_TimeService and select the time service instance. Click on the Methods tab to highlight the available methods on this instance:
Figure 19: The instance editor showing available methods for the time service instance
We can see the ManageSystemTime method. Highlight that and, from the Object menu, choose Execute Method. You will see a new window pop up that looks like this:
Figure 20: The execute method dialog
From here, we can either set or retrieve the system time. If you know how many seconds it has been since January 1, 1970, you can go ahead and enter that in the value column for TimeData. I'm going to enter the value true for GetRequest, and it will retrieve the system time. I enter true and hit return, then click Execute.
The result is not quite what I expected, at first glance. I get a message dialog that indicates that the command ran to completion successfully return code of 0 and I can click OK. Where is the system time?
The system time was sent back as a value in the out array, but wasn't interpreted by the SNIA browser. However, if I'm running the CIMOM in debug mode, I can see the value returned to me in the trace:
[5011] CIMServer finished invoking extrinsic method provider: /root/cimv2:NovlDevServ_TimeService.SystemCreationClassName="Novell_LinuxUnitaryComputerSystem",SystemName="plankton.provo.novell.com",CreationClassName="NovlDevServ_TimeService",Name="TimeService".ManageSystemTime OUT Params(TimeData=20041202190505.754897-420) return value: 0
If you look at this, you can see the time as the first OUT parameter. It is December 2, 2004, at 19:05:05. Now I can try setting the time. I run Execute Method on Manage System Time again, and get the window above. This time I enter false for GetRequest. For TimeData, I enter 20041202023200.000000-420 to set the time to December 2, 2004, at 02:32:00. Note: Be sure to click outside of the data entry box in order for the value to be set! After doing this, I click Execute.
The system says things ran to a successful completion. If I enter the date command at my command prompt, I get:
Thu Dec 2 02:32:39 UTC 2004
So it looks like I successfully set my system time to a different time.
© 2008 Novell, Inc. All Rights Reserved.