This tutorial shows how to design and implement your own CIM schema for a CIM provider. This tutorial implements CIM schema extensions for a time service provider, based upon the tutorial Developer Primer to WBEM and CIM.
Contents |
The first thing that we need to do when extending the capability of CIM and WBEM is to extend the CIM schema. What we will actually do is:
This is the most critical portion of the entire exercise. Failure to correctly model our application may result in a model that is not easy to work with from a client perspective, in information that is not easily retrievable by interested third parties, or even a schema that is not meaningful or consistent with CIM. However, to the degree that we are successful in modeling our application, the opposite will be true â we will be more consistent with the CIM standard, making our data easily retrievable by interested third parties, and easy to work with from a client.
First, we need to decide upon a schema name for our schema. I will choose one that pertains to my company and organization:
NovlDevServ
Note that âCIMâ? and âPRSâ? are reserved names. Additionally, you will probably want to avoid using the âNovellâ? schema name, since this is the name of all Novell schema.
We will be creating several different classes. A single class is used to manage a single element. Class names follow the convention schemaname_classname. So for example, a class that I create for the purpose of modeling a Unix Domain Socket for MyApplication may have the name:
NovlDevServ_MyApplicationUnixDomainSocket
We can refer to the CIM meta schema diagram to see what else a class can contain, such as:
We also see two special types of classes, associations and indications. Associations have at least two of a special type of property called a reference, which refers to another class instance. CIM is unique in this way. In most object-oriented languages, classes that are associated implement this association via containment â one class contains an instance of another class, or a pointer or reference to another instance of another class. In CIM, Association classes are the ONLY classes that can contain references. Associations must contain at least two references and may not contain other types of properties. So, a line in a UML diagram that represents an association between two classes may, in C++, be represented by a pointer to an instance of the second class from within an instance of the first. But in CIM, that association is implemented as it's own class, which connects the other classes, and that association class itself can be a child class in a separate inheritance hierarchy.
This will make better sense as we conduct our modeling exercise.
The first thing we need to do is, essentially, to conduct an object modeling exercise, where we try to represent the major objects in our system. The object model we create will serve as the foundation of the MOF file that we will create later to load into the CIMOM.
You can use any object modeling software you like for this exercise, such as Rational Rose, or do it by hand. We will be using UML to represent the object model. The diagrams in this document were created using Dia, an open-source diagram editing tool. Dia is available with most modern Linux distributions, or you can download it here.
What are some of the major components of the time service system?
We know that a given Linux machine probably has the network time protocol (NTP) service that manages the system time. This service can be (and usually is) configured to start at boot time, and can be stopped and started by a user with the appropriate permission. We also know that the time service has a configuration file that tells it where to look for time service providers.
Less obvious is another key piece of the puzzle, which is the network connection endpoint that resides on this system in order to deliver the time service capability. It is helpful to remember that, with CIM, we model both logical and physical components, both software and hardware components, where they make sense. Of course, for a given application, it doesn't usually make sense to indicate that the application uses memory or CPU, but it is helpful in our case to know that the service requires a network connection endpoint.
One way to consider this issue is thus: How would a client like to be able to find out information about this service? Including the network connection is a good idea in this case. A client may choose to look at all open network connections on a machine, then drill down on a particular connection to see that the connection in question pertains to the time service, for example.
Here's our model diagram at this point. We simply indicate that we think there are three classes: the time service, the time service configuration, and the time service port, where the connection endpoint resides.
Figure 3: Basic time service objects
Next we probably need to get an idea about what CIM classes each of these classes will extend or inherit from. As we scour the CIM schema and the Novell CIM SDK schema, we get an idea of what base classes to use. When creating a provider to run on OES, we should always use classes from the Novell CIM SDK schema if possible. It also helps if we consider the names of the classes that we are deriving from in the names we give our classes.
Additionally, we remember that we need to use the name of our schema in our class names:
Figure 4: Inheritance hierarchy of basic time service objects
The objects that are grayed out are existing objects. The classes beginning with âCIMâ? are a part of the CIM schemas; the classes beginning with âNovellâ? are part of the Novell schema extensions to CIM. There are only three classes that we need to worry about implementing â NovlDevServ_TimeService, which used to be TimeService; NovlDevServ_RemoteTimeServicePort, which used to be TimeServicePort; and NovlDevServ_TimeZoneSettingData, which used to be TimeServiceSettings.
Note that we don't always have to create our own class for a particular portion of our model, particularly if there is an existing class in the schema that is sufficiently detailed to represent our object. In our case, we found classes that were sufficient generalizations, but we needed to extend those classes for our specific needs.
Important Note:
The current production CIM schema is the CIM 2.8 schema; however, the CIM 2.9 schema is currently being worked on and will be the production CIM schema in the near future. In our model we have derived our class NovlDevServ_TimeService from the class Novell_TimeService. We are actually being a bit forward-thinking here; in the CIM 2.9 schema, there is a CIM_TimeService class from which Novell will eventually derive Novell_TimeService, and future versions of the CIM SDK will reflect this change. However, the current SDK does not contain the Novell_TimeService class.
This isn't really that big of a problem. We have a couple of options:
In this example we are going to select the second option, and implement the dummy class. We can see this represented in the UML diagram above. Either option will work equally well at this point.
Next we need to determine how these different classes will interact with each other. Not only do our classes inherit from CIM classes, but they also have defined relationships between each other that also need to be captured.
For example, we will have only one time service on a box, but that service may provide service over any number of time service ports. We will want the ability to identify all the ports that accompany a particular service.
Here's a shot at what we think these relationships might look like:
Figure 5: Time service object layout, with associations
Notice the relationships that are defined in this diagram:
A critically important thing to remember when modeling for CIM is that associations are also classes, and like classes, they can inherit from parent classes, and should do so whenever possible and reasonable. A separate diagram will help us see the classes from which our association classes are derived:
Figure 6: Inheritance hierarchy of basic time service association objects
An astute viewer will notice that the relationship between Novell_LinuxUnitaryComputerSystem and NovlDevServ_TimeService is represented differently than the relationship between NovlDevServ_TimeService and NovlDevServ_RemoteTimeServicePort or NovlDevServ_TimeService and NovlDevServ_TimeZoneSettingData (see Figure 5). The dashed arrow represents a true dependency relationship between the two objects.
If we look at the association objects inheritance hierarchy (Figure 6), we can see that the NovlDevServ_TimeServiceSettingData relationship is derived from CIM_ElementSettingData, and does not come from CIM_Dependency, so that particular relationship is not a dependency relationship. In other words, it is not necessary for the time zone to be set in order for the time service to function.
The other two relationships, however, do inherit from CIM_Dependency. But if the dashed arrow represents a dependency relationship, why isn't the NovlDevServ_TimeServiceAccessBySAP relationship represented with a dashed arrow?
What we are seeing here is a subtle interpretation of the time service and how things are associated to each other. For exactly one instance of NovlDevServ_TimeService, there must be exactly one Novell_LinuxUnitaryComputerSystem that is associated with it. It does not make sense to say that the time service exists unless you can make a determination as to which computer system hosts it.
However, the relationship between NovlDevServ_TimeService and NovlDevServ_RemoteTimeServicePort is slightly different. For example, it does make sense to say that the time service is providing only local time service, and is not connected to any remote port at all, so it is certainly possible to have an instance of NovlDevServ_TimeService with no associated instance of NovlDevServ_RemoteTimeServicePort. However, if the time service is offering remote time service capability, then it does depend upon at least one instance of NovlDevServ_RemoteTimeServicePort. So there is still a dependency, but the dependency is more due to the need for an instance of an object if a certain feature is being used, not due to the existence of another object altogether, as it was in the first case.
At this point we have basically completed the model. Actually, we have created three distinct diagrams: one that describes the basic objects in our diagram and their relationships with each other and the computer system that hosts them; one that describes how the new objects are derived from base classes within the CIM and Novell schemas; and one that describes how the associations for these new classes are derived from base association classes within the CIM schemas.
My preference is to group all of these diagrams in a single diagram image:
Figure 7: Time Service Provider model
One thing that is important to note here is that it is more important for the model of our application to fit within the CIM model than it is for the model to be faithful to the application itself. When you work with CIM, it is important to understand the CIM models and schemas, and make your model fit within those models and schemas that CIM defines, as well as possible. This will make it easier for clients that understand CIM, but don't understand your application well, to obtain information from your model and even execute methods within your model.
Some developers that look at CIM feel wont to criticize the model and do things their own way instead of trying to fit within the model that is provided. These developers fail to understand the way in which they are frustrating their own efforts by deviating from the model that CIM provides. Open standards necessarily have shortcomings that make them imperfect for every possible application. At the same time, there are significant advantages in complying with open standards, not the least of which are extensibility and interoperability.
Developing to CIM is not necessarily easy, and failure to stay within the model means that the result is limited in its ability to benefit from the extensibility and interoperability promised by compliance with the standard. In other words, developers that don't want to conform to the CIM standard way of doing things would be better off to not try to use CIM at all. If, however, you want to gain the advantages of living within the CIM standard, it is worth the extra effort to try to make sure your model is as compliant with the standard as possible.
The next thing we need to do is create a MOF file. The MOF file is basically a textual representation of the object model that we have already created. When our MOF file is finished, it will be compiled and loaded by the CIMOM's MOF compiler, which causes the schema for the CIMOM to be extended in the ways we specify in the MOF file.
Creating the MOF file is not as difficult as it seems initially, especially if we have an example to work from. Most of the information we captured in our UML diagram will also be represented in the MOF file. Once we learn the syntax of MOF and the identifiers available to us, creating a MOF file from the UML should be a piece of cake.
While creating the MOF file, we will want a couple of references handy. The first is the CIM specification. The second is the actual MOF files for the CIM and Novell schemas. We will want these MOF files as reference material for our classes that derive from classes in these MOF files.
The easiest way to start out our MOF file is to simply stub it out. We know that we need to define each new class that we have added for our application. Remember, we have six new classes, including three new regular classes â NovlDevServ_TimeService, NovlDevServ_RemoteTimeServicePort, and NovlDevServ_TimeZoneSettingData â and three new association classes â NovlDevServ_HostedTimeService, NovlDevServ_TimeServiceAccessBySAP, and NovlDevServ_TimeServiceSettingData.
We also remember that we decided to add the dummy Novell_LinuxService class.
Here's what our stubbed-out MOF file looks like:
#pragma locale ("en_US")
class Novell_TimeService : CIM_Service
{
};
class NovlDevServ_TimeService : Novell_TimeService
{
};
class NovlDevServ_RemoteTimeServicePort : CIM_RemotePort
{
};
class NovlDevServ_TimeZoneSettingData : CIM_SettingData
{
};
class NovlDevServ_HostedTimeService : CIM_HostedService
{
};
class NovlDevServ_TimeServiceAccessBySAP : CIM_ServiceAccessBySAP
{
};
class NovlDevServ_TimeServiceSettingData : CIM_ElementSettingData
{
};
Not too interesting, but also not too difficult â in fact, at this point it looks pretty much like C++. Let's walk through the complete implementation of a couple of classes, to get us started: NovlDevServ_TimeZoneSettingData and NovlDevServ_TimeServiceSettingData.
At this point we should refer back to the CIM meta schema diagram (Figure 1). It gives us hints to the things we need to consider for our classes in our MOF file because it helps us understand what a class can be comprised of and related to. Basically, for each regular class, we need to consider the following:
Qualifiers are basically values that are added to a property, method, or class to give it special characteristics. Qualifiers are used to indicate many different things; check the CIM specification for details on qualifiers and where they apply.
Qualifiers are indicated in square brackets prior to the class, method, or property that they qualify, and are separated by commas. In most cases, qualifiers are not order-specific, but there are certain qualifiers that must come first. Check the specification for details.
Each of our classes can have properties, and we need to be sure the MOF correctly defines the properties we have identified for each class. Some of the things we need to consider are:
Refer to the CIM and Novell MOF files to determine the information you need about parent classes. Refer to the CIM specification for information about qualifiers that you can apply to a property.
Let's consider our two examples.
Keys â NovlDevServ_TimeZoneSettingData is a regular class that derives from CIM_SettingData. A quick scan of the parent class shows us that CIM_SettingData is an abstract class, but that it defines the key: InstanceID. We can't change the keys, but we know that there is only one possible instance of this class, so we can at least invent a static key and document the static key. We will actually implement the static key in our code. NovlDevServ_TimeServiceSettingData is an association class, and the keys of the class are the two reference properties. In our case, they are named ManagedElement and SettingData. We always know what classes each property refers to and we can so indicate in the MOF file.
Inherited Properties â Neither class has any other inherited properties that we need to consider. Of course, the properties that were keys we needed to consider, but we already discussed those.
New Properties â NovlDevServ_TimeZoneSettingData has a couple of new properties we need to consider. We get an idea of the properties we need from the actual clock configuration file, which is located in /etc/sysconfig/clock on my SUSE workstation. The two values we care about from this value are the settings for the time zone and for whether we are using UTC clock time or local time for the hardware clock. We could also implement the default time zone value, if desired, but it is not necessary for this example.
Here are the updated versions of our two classes. We can see the qualifiers for the properties enclosed within the square brackets. The properties themselves look mostly like regular variable declarations. The exception is the special REF type, which applies only to association classes, so be sure to note the special syntax in the second class below.
class NovlDevServ_TimeZoneSettingData : CIM_SettingData
{
[ Key, Description(
"InstanceID is the key field inherited from CIM_SettingData. "
"Since there is only ever one instance of this class, the key "
"value is always 'NOVLDEVSERV:SUSE:TIMEZONE' ") ]
string InstanceID;
[ Required(true), Description("The time zone setting from /etc/sysconfig/clock."),
Write(true) ]
string TimeZone;
[ Description(
"Determines whether the hardware clock is set to UTC or local time. "
"If the value of UTCHardwareClock is set to true, then the hardware "
"clock is set to UTC, which is the value of '-u'. Otherwise, the "
"hardware clock is set to local time, with the value of '--localtime'."),
Write(true) ]
boolean UTCHardwareClock = true;
};
class NovlDevServ_TimeServiceSettingData : CIM_ElementSettingData
{
[ Override("ManagedElement") ]
NovlDevServ_TimeService REF ManagedElement;
[ Override("SettingData") ]
NovlDevServ_TimeZoneSettingData REF SettingData;
};
Adding class methods is pretty much the same as adding class properties. We consider the methods provided by the base classes, if any, and override them if necessary. Of course, if we inherit from an abstract base class, we will need to be sure to override the methods within our class.
We may also choose to add our own methods. Usually we will only do this if our instance is supposed to be able to execute something on the local machine. Association classes will never have methods associated with them.
In our case, there is actually only one class with methods â NovlDevServ_TimeService. The time service class needs to be able to get and set the system time. We are going to implement one method that will both set the time and get the time. Of course, it would be possible to implement two different methods to do this, but this is the way the CIM_TimeService class does it (in the CIM 2.9 schema) and we want to try to be compliant with that interface.
This method will either get or set the system time, and return a success code that we will define. We will use CIM's ValueMap and Values qualifiers to help us out. The ValueMap qualifier allows us to define a range of acceptable values for a property or method. The Values qualifier allows us to indicate the meaning of the values defined in the value map.
We specify a definition for two methods in our MOF file, but we are only going to provide code for one. We indicate the lack of support for the second by marking it as INVISIBLE and indicating that it is not implemented in the description. In this particular case, we do this because this method doesn't really apply in our case. This unimplemented method allows us to manage the time for any managed element as passed into the method, but we always know the one managed element for our time service, so this method is not necessary.
Here's our NovlDevServ_TimeService class with the methods added:
class NovlDevServ_TimeService : Novell_TimeService
{
[ Static(true),
Description (
"This method retrieves the time for the specified element."),
ValueMap { "0", "1", "2", "3", "4", "5", "6..32767", "32768..65535" },
Values { "Success", "Not Supported", "Unknown", "Timeout",
"Failed", "Invalid Parameter", "DMTF Reserved",
"Vendor Specific" } ]
uint32 ManageSystemTime (
[ IN, DESCRIPTION (
"Indicates what type of request this is: "
"TRUE - Get time request "
"FALSE - Set time request ") ]
boolean GetRequest,
[ IN, OUT, DESCRIPTION (
"If GetRequest is TRUE, then the value of this parameter is ignored; "
"otherwise, the time on the specified element is set to this value. "
"On output, this value contains the time as known by the element.") ]
datetime TimeData );
[ INVISIBLE(true),
Description (
"NOT IMPLEMENTED - DON'T USE. "
"This method is inherited from CIM_TimeService. "
"Since this method always pertains to the ComputerSystem, "
"The ManagedElement element parameter is not necessary. "
"For this reason the ManageSystemTime method should be "
"used instead of ManageTime") ]
uint32 ManageTime (
[ IN ]
boolean GetRequest,
[ IN, OUT ]
datetime TimeData,
[ REQUIRED, IN ]
CIM_ManagedElement REF ManagedElement);
};
By now we've added qualifiers to methods and properties. Qualifiers for classes is not that different from those applied to methods and properties. In particular, we will probably want to apply a good description to each class for the sake of good documentation.
One particular qualifier we need to consider is the âAssociationâ? qualifier that applies to association classes â in fact, the existence of this qualifier is what makes it an association class. Association is one of those qualifiers that must be the first qualifier listed in order to make the class an association class.
Other key qualifiers for classes include:
We have three association classes, of which NovlDevServ_TimeServiceSettingData is one. Here's an example of this class with the class qualifiers added:
[ Association,
Description(
"NovlDevServ_TimeServiceSettingData associates the time service "
"with the time zone configuration data.") ]
class NovlDevServ_TimeServiceSettingData : CIM_ElementSettingData
{
[ Override("ManagedElement") ]
NovlDevServ_TimeService REF ManagedElement;
[ Override("SettingData") ]
NovlDevServ_TimeZoneSettingData REF SettingData;
};
Here is the complete MOF file. You can get a copy of the up-to-date MOF file for this sample provider from the Novell CIM SDK project page on Novell Forge.
#pragma locale ("en_US")
[ Abstract ]
class Novell_TimeService : CIM_Service
{
};
[ Description (
"NovlDevServ_TimeService represents the configuration and function of system time.") ]
class NovlDevServ_TimeService : Novell_TimeService
{
[ Key, Override("SystemCreationClassName"),
Propagated("Novell_LinuxUnitaryComputerSystem") ]
string SystemCreationClassName;
[ Key, Override("SystemName"),
Propagated("Novell_LinuxUnitaryComputerSystem") ]
string SystemName;
[ Key, Override("CreationClassName") ]
string CreationClassName = "NovlDevServ_TimeService";
[ Key, Override("Name") ]
string Name = "timeservice";
[ Static(true),
Description (
"This method retrieves the time for the specified element."),
ValueMap { "0", "1", "2", "3", "4", "5", "6..32767", "32768..65535" },
Values { "Success", "Not Supported", "Unknown", "Timeout",
"Failed", "Invalid Parameter", "DMTF Reserved",
"Vendor Specific" } ]
uint32 ManageSystemTime (
[ IN, DESCRIPTION (
"Indicates what type of request this is: "
"TRUE - Get time request "
"FALSE - Set time request ") ]
boolean GetRequest,
[ IN, OUT, DESCRIPTION (
"If GetRequest is TRUE, then the value of this parameter is ignored; "
"otherwise, the time on the specified element is set to this value. "
"On output, this value contains the time as known by the element.") ]
datetime TimeData );
[ INVISIBLE(true),
Description (
"NOT IMPLEMENTED - DON'T USE. "
"This method is inherited from CIM_TimeService. "
"Since this method always pertains to the ComputerSystem, "
"The ManagedElement element parameter is not necessary. "
"For this reason the ManageSystemTime method should be "
"used instead of ManageTime") ]
uint32 ManageTime (
[ IN ]
boolean GetRequest,
[ IN, OUT ]
datetime TimeData,
[ REQUIRED, IN ]
CIM_ManagedElement REF ManagedElement);
};
[ Description(
"NovlDevServ_RemoteTimeServicePort represents the NTP configuration for the time service.") ]
class NovlDevServ_RemoteTimeServicePort : CIM_RemotePort
{
[ Override("AccessInfo"), Write(true),
Description (
"Access and/or addressing information for a remote "
"connection. This can be a host name, network address or "
"similar information. This is inherited from "
"CIM_RemoteServiceAccessPoint"),
ModelCorrespondence { "CIM_RemoteServiceAccessPoint.InfoFormat"} ]
string AccessInfo;
[ Override("PortInfo"), Write(true),
Description("Address of remote NTP server. This is "
" inherited from CIM_RemotePort. This will be in "
" the form of a DNS name of an IP address. "
" A port may be specified in this field by using "
" the ':' character as a delimeter. Example: "
" 137.65.1.1:123 or time.server.com:123 "
" If not port is specified the NTP well known port "
" (123) will be assumed") ]
string PortInfo;
[Description ("An enumerated integer describing the protocol of the port "
"addressed by PortInformation."),
ValueMap { "1", "2", "3", "32768..65535" },
Values { "Other", "TCP", "UDP", "Vendor Specific" },
ModelCorrespondence { "CIM_RemotePort.OtherProtocolDescription"}]
uint16 PortProtocol = 2;
string Options;
boolean Prefer;
};
[ Description(
"NovlDevServ_TimeZoneSettingData represents the configuration of the system time zone.") ]
class NovlDevServ_TimeZoneSettingData : CIM_SettingData
{
[ Key, Description(
"InstanceID is the key field inherited from CIM_SettingData. "
"Since there is only ever one instance of this class, the key "
"value is always 'NOVLDEVSERV:SUSE:TIMEZONE' ") ]
string InstanceID;
[ Required(true), Description("The time zone setting from /etc/sysconfig/clock."),
Write(true) ]
string TimeZone;
[ Description(
"Determines whether the hardware clock is set to UTC or local time. "
"If the value of UTCHardwareClock is set to true, then the hardware "
"clock is set to UTC, which is the value of '-u'. Otherwise, the "
"hardware clock is set to local time, with the value of '--localtime'."),
Write(true) ]
boolean UTCHardwareClock = true;
};
[ Association,
Description(
"NovlDevServ_HostedTimeService associates the time service to the computer system.") ]
class NovlDevServ_HostedTimeService : CIM_HostedService
{
[ Override("Antecedent"), Min(1), Max(1),
Description("The hosting Linux system") ]
Novell_LinuxUnitaryComputerSystem REF Antecedent;
[ Override("Dependent"), Weak,
Description("The hosted time service") ]
NovlDevServ_TimeService REF Dependent;
};
[ Association,
Description(
"NovlDevServ_TimeServiceAccessBySAP associates the NTP access configuration to "
"the time service.") ]
class NovlDevServ_TimeServiceAccessBySAP : CIM_ServiceAccessBySAP
{
[ Override("Antecedent"),
Description("The time service") ]
NovlDevServ_TimeService REF Antecedent;
[ Override("Dependent"),
Description("Access point for the time service") ]
NovlDevServ_RemoteTimeServicePort REF Dependent;
};
[ Association,
Description(
"NovlDevServ_TimeServiceSettingData associates the time service "
"with the time zone configuration data.") ]
class NovlDevServ_TimeServiceSettingData : CIM_ElementSettingData
{
[ Override("ManagedElement") ]
NovlDevServ_TimeService REF ManagedElement;
[ Override("SettingData") ]
NovlDevServ_TimeZoneSettingData REF SettingData;
};
Once you have finished with your MOF file, you run:
owmofc <mof_file_name>
This will compile your MOF file, and, if the compile is successful, it will install your schema into the CIMOM. The CIMOM must be running in order for your MOF compilation to execute successfully.
After you have successfully compiled and installed your MOF file, you should be able to load your CIM browser, and locate the class definitions of each classes by browsing through the class hierarchy on the left.
© 2008 Novell, Inc. All Rights Reserved.