SMP 行为和交互

内部方法和管理基础结构 API

存储管理提供程序(SMP)开发人员使用:

  • 由Convert-MofToProvider.exe生成的固有方法。
  • mi.h 文件中的管理基础结构 (MI) API 提供其 SMP 的实现。

以下项目符号记下几个关键内部函数和 MI 方法。

  • EnumerateInstancesGetInstance

    当有特定类实例的查询时,将调用 EnumerateInstances。 例如:PowerShell cmdlet Get-Object<> 映射到相应的 WMI 对象的 EnumerateInstances 方法。 此方法应通过 <Object>_Post 方法返回类的所有实例。 由于 WMI 经常调用 EnumerateInstances,因此它应快速执行。 为此,请使用良好的缓存管理。

    需要特定类实例时调用 GetInstance,例如(但包括但不限于):

    • 当 WMI 基础结构调用此类的任何方法时
    • 当基于 WMI 的应用程序直接调用此方法时
    • 通过关联类请求类的实例时

    GetInstance 方法应仅返回通过 <Object>_Post 方法指定的对象。 要查询的实例的标识符,即 MOF 中定义的“Key”,通常是 ObjectId,通过 InstanceName 参数检索。 此方法经常由 WMI 调用,应快速完成。

    EnumerateInstances 和 GetInstance 对于常规类(如 StorageProvider、StorageSubsystem、PhysicalDisk 等)是必需的。

    对于 Association 类,EnumerateInstances、AssociatorInstances 和 ReferenceInstances 是必需的,而 GetInstance 不是。

  • <对象>_PostMI_PostResult

    若要了解 MI API 方法 <对象>之间的差异_Post和MI_PostResult:

    • <将 Object>_Post视为返回指向输出参数的指针。
    • 将MI_PostResult视为指示函数的执行状态的函数返回值。

    每个 WMI 方法“context”只能调用MI_PostResult一次,这可在每个 WMI 方法的输入参数中找到。 “Context”是指向 WMI 回调的指针。 调用MI_PostResult将销毁此指针。 因此,不应在另一个 WMI 方法的正文中调用 WMI 方法。

    <另一方面,每个 WMI 方法上下文可以多次调用 Object>_Post。 此方法通常用于 EnumerateInstances 以返回多个对象。

  • 设置<属性>ModifyInstance

    Windows 存储管理 API 不支持内部方法 ModifyInstance。 若要修改对象的属性,将使用 extrinsic 方法 Set<属性> 。

有关内部方法和 MI API 的详细信息,请参阅 Windows SDK 中的 MI API 示例。

对象标识

SMP 接口使用以下两组属性进行对象标识:

  • 对于脚本和编程:ObjectId 和 UniqueId

    ObjectId 是创建和维护的不透明标识符,用于使用 SMP 及其客户端来跟踪对象的实例。 它是全局唯一所必需的属性。 也就是说,即使两个对象由单独的 SMP 管理或位于不同的存储子系统上,也不应具有相同的 ObjectId。

    如果对象通过两个不同的路径可见(例如:有两个单独的 SMP 指向同一存储子系统),则同一对象可以显示两个不同的 ObjectId。 若要确定两个对象实例是否为同一对象,请使用 UniqueId 属性。

    UniqueId 是一个必需属性,用于唯一标识全局范围内类的实例。 此值应在不同管理服务器上运行的两个 SMP 实例之间相同。 与 ObjectId 不同,UniqueId 应该是存储子系统保留的值,而不是存储管理提供程序进程。

    UniqueId 可以是任何不透明值,除非另有说明(例如:MSFT_VirtualDisk)。

  • 显示: FriendlyNameName

    最终用户使用这两个属性标识对象。 FriendlyName 是最终用户可设置的用户友好字符串(如果 SMP 支持此类操作)。 FriendlyName 不一定是唯一的。 单个存储子系统中的两个对象可以共享相同的 FriendlyName,尽管不建议这样做。

    SMP 设置 Name 属性,最终用户无法对其进行修改。 SMP 提供此属性中的其他信息,以帮助最终用户识别对象。 这些信息可能涵盖对象的技术方面。 例如,存储子系统的名称可以是子系统的 IP 或 WWN。 名称在特定范围内通常是唯一的。 例如,存储池的名称在拥有的存储子系统中必须唯一。

错误处理

SMP 接口中有三种类型的错误: Windows 存储管理 API (SM API ) 返回代码、“软错误”和“硬错误”。

SM API 返回代码引用为每个 SMP 外行方法列出的错误代码作为返回值。 例如,“5”表示“参数无效”。 这些错误代码通过Convert-MofToProvider.exe生成的方法结构中定义的 MIReturn 输出参数返回。 MIReturn 的值可以通过 Object> _<Method> 设置<_Set_MIReturn在相应对象的头文件中定义。

在可能的情况下,Extrinsic 方法应始终默认使用 SM API 错误代码。 如果需要其他信息,SMP 可以使用 MSFT_ExtendedStatus 类提供有关外向方法调用的额外状态信息。 此方法最好对外向方法使用软错误。

软错误是指通过 MSFT_SoftError 类返回的错误消息。 这些错误专为内部方法(EnumerateInstances、GetInstance 等)而设计,因为无法返回 SM API 错误代码。 若要返回软错误,应通过 mi.h 中定义的 MI_WriteCimError 方法中的“MI_Instance error”参数构造和返回派生自MSFT_SoftError的软错误类的实例。 例如,若要指示存储阵列登录期间“需要正确的凭据”,可以在对 StorageSubsystem 对象的 EnumerateInstances 调用期间返回“MSFT_SoftError_NotAuthenticated”实例。 对于软错误,仍应通过MI_PostResult发布MI_RESULT_OK的结果。

硬错误是指 mi.h 文件中MI_Result结构中定义的错误。 MI API 返回这些错误。 除非绝对必要,否则 SMP 应避免将这些错误直接呈现给存储管理应用程序。 例如,对于“无效参数”,SMP 应使用 MIReturn 来显示 SM API 错误代码“5”- “无效参数”,而不是依赖存储管理应用程序来使用MI_RESULT_INVALID_PARAMETER。

原始池

原始池(也称为“可用存储”池)是在创建和删除具体存储池时绘制和返回存储容量的位置。 无法创建、删除或修改原始池。

SMP 必须至少提供一个原始池。 将物理磁盘添加到具体的存储池时,仍应将物理磁盘视为原始池的一部分。

大小报告

对于存储池对象中的各种大小字段,有两种特殊情况可以讨论:热备用驱动器的容量和不正常的驱动器的容量。

将驱动器指定为热备用驱动器后,其容量应包含在相应的原始池的 AllocatedSize 中。 但是,驱动器的容量不应包含在任何具体池的大小中,即使存储阵列支持将热备用驱动器分配给特定的混凝土池也是如此。 将热备用驱动器专用于特定的混凝土池后,该驱动器的容量不应包含在混凝土池的 AllocatedSize 中,直到实际更换已用驱动器为止。 添加到具体池时,CanPooled 对于此热备用驱动器的物理磁盘对象应为 FALSE。 应在此物理磁盘对象与具体池的存储池对象之间创建关联。

“不正常”的 HealthStatus 驱动器的容量不应包含在原始池或具体池的任何大小字段中。

关联

SM API 包括定义存储对象之间的关系的关联类。 使用这些关联类,可以轻松遍历存储对象层次结构以获取给定对象的相关对象。 对于存储 PowerShell 模块,cmdlet 管道是通过关联类实现的。 例如,给定虚拟磁盘对象后,用户可以通过以下 cmdlet 获取拥有虚拟磁盘对象的存储池:

    PS> Get-VirtualDisk –FriendlyName MyVirtualDisk | Get-StoragePool

本部分的其余部分说明了关联类的实现。 注释中的方法由每个关联类的Convert-MofToProvider.exe生成。 注释使用 XToY 作为示例关联类;伪代码使用 StoragePoolToVirtualDisk 作为示例。

  • EnumerateInstancesGetInstance
      - XToY\_EnumerateInstances returns association objects (XToY objects) for ALL X objects
    
    <!-- end list -->
    
        void MI_CALL SAMPLE_StoragePoolToVirtualDisk_EnumerateInstances( ... )
        {
            ...
        
        /** This method should return association objects for ALL Storage Pools. **/
        
            // for each storage pool
        
                // for each virtual disk that's associated with this storage pool
        
                    // create the StoragePoolToVirtualDisk association object
                    // set the storage pool object and virtual disk object to this association object
                    // post the association object
                
                // end for
        
            // end for
        
            ...
        }
  • AssociatorInstances
      - AssociatorInstances method returns regular objects instead of association objects
      - XToY\_AssociatorInstancesX should return all associated Y object(s) for the X specified
      - XToY\_AssociatorInstancesY should return all associated X object(s) for the Y specified
    
    <!-- end list -->
    
        void MI_CALL SAMPLE_StoragePoolToVirtualDisk_AssociatorInstancesStoragePool(...)
        {
            ...
        
        /** This method should return VIRTUAL DISK object(s) for the 
        STORAGE POOL specified. **/
        
            // for each virtual disk that's associated with this storage pool
        
                // create the virtual disk object
                // post the virtual disk object
                
            // end for
        
            ...
        }
        
        void MI_CALL SAMPLE_StoragePoolToVirtualDisk_AssociatorInstancesVirtualDisk(...)
        {
            ...
        
        /** This method should return STORAGE POOL object(s) for the 
        VIRTUAL DISK specified. **/
        
            // for each storage pool that's associated with this virtual disk
        
                // create the storage pool object
                // post the storage pool object
                
            // end for
        
            ...
        }
  • ReferenceInstances
      - ReferenceInstances is similar to AssociatorInstances except that these methods return association (XToY) objects instead of regular objects
      - XToY\_ReferenceInstancesX should return XToY object(s) for X specified
      - XToY\_ReferenceInstancesY should return YToX object(s) for Y specified
    
    <!-- end list -->
    
        void MI_CALL SAMPLE_StoragePoolToVirtualDisk_ReferenceInstancesStoragePool(...)
        {
            ...
        
        /** This method should return StoragePoolToVirtualDisk 
        ASSOCIATION object(s) for the STORAGE POOL specified. **/
        
            // for each virtual disk that's associated with this storage pool
        
                // create the StoragePoolToVirtualDisk association object
                // set the storage pool and virtual disk to this association object
                // post the association object
                
            // end for
        
        
            ...
        }
        
        void MI_CALL SAMPLE_StoragePoolToVirtualDisk_ReferenceInstancesVirtualDisk(...)
        {
            ...
        
        /** This method should return StoragePoolToVirtualDisk 
        ASSOCIATION object(s) for the VIRTUAL DISK specified. **/
        
            // for each storage pool that's associated with this virtual disk
        
                // create the StoragePoolToVirtualDisk association object
                // set the storage pool and virtual disk to this association object
                // post the association object
                
            // end for
        
            ...
        }

缓存管理

加载 SMP 时,它应初始化存储对象的缓存。 此初始化可确保在服务 API 调用时快速响应时间,因为可以直接从 SMP 的缓存中检索对象。 此缓存应与带内对象更改和带外对象更改保持同步。

带内对象更改包括通过当前 SMP 实例进行的这些更改。 例如,如果通过当前 SMP 实例创建虚拟磁盘:

  • 应将新的虚拟磁盘对象添加到缓存中。
  • 还应更新关联的对象,例如拥有存储池和关联的目标端口对象。

带外更改包括通过供应商专有工具和在其他计算机上托管的 SMP 进行的这些更改。 例如,如果通过供应商专有工具创建虚拟磁盘,则应将事件从存储子系统发送到 SMP(s),以触发缓存更新。

调用存储提供程序类中的 Discover 方法时,SMP 还应更新缓存。 存储管理应用程序调用此方法,以便在服务重启或系统重启等事件上重置和重新生成缓存。

如果 SMP 无法在启动时初始化整个缓存(由于对象过多或无法快速完成),则只能将存储提供程序和存储子系统对象加载到缓存中。 应用程序将查看存储子系统对象上的 CurrentCacheLevel 属性,以了解缓存的填充深度。 最终用户或应用程序通过 Discover 方法显式加载缓存的其余部分。

异步操作

完成时间超过 30 秒的任何操作都必须返回存储作业对象。 包含 CreatedStorageJob 输出参数的方法很可能属于此类操作。 SMP 应实现所有这些方法作为异步操作,并为其返回存储作业对象。 存储作业对象必须在 30 秒内返回到调用方;否则,如果调用方等待时间过长,但仍未收到存储作业对象,则调用方可能会超时。

应用程序(或“WMI 客户端”)可以选择指定方法是否应为“RunAsJob”。 应用程序使用的 SM API 包含此额外的布尔 RunAsJob 参数和 CreatedStorageJob 输出参数。 同时,SMP 接口中的相应方法只有 CreatedStorageJob 参数。 但是,无论“RunAsJob”的值如何,SMP 应始终返回这些方法的存储作业对象。

以下方案说明了异步操作的调用顺序。 CreateVirtualDisk 用作示例:

  • 如果“RunAsJob”设置为 TRUE

    调用 CreateVirtualDisk 时,SMP 应为该方法执行初始化,在存储子系统中启动作业,并在 30 秒内将存储作业对象返回到调用方。 但是,存储子系统可能需要花费任何时间来完成操作。 在此期间,调用方将轮询作业的状态。

    工作线程应用于执行作业。 为了提高效率,仅当调用方轮询该作业的状态时,SMP 才能更新与作业状态相关的属性(例如 PercentComplete)。

  • 如果“RunAsJob”设置为 FALSE

    调用方将在 CreateVirtualDisk 方法上被阻止,直到该方法返回。 SM API 会自动执行阻止和轮询本身。 这种类型的调用方通常是非用户交互式客户端(例如脚本工具),它更喜欢阻止机制。

    由于获取有关新创建对象的信息的唯一方法是通过此对象与相应的存储作业对象之间的关联,因此 SMP 应在从缓存中删除存储作业对象之前至少保留 24 小时。 对于不返回新创建的对象的其他操作(例如 DeleteObject 操作),不需要关联,并且存储作业对象只需保持活动状态 15 分钟。

对于管理控制台意外的系统重启,SMP 应在物理位置(例如存储阵列)维护 StorageJob 对象的缓存,并在系统重启时重新加载缓存。

提供商生命周期控制

可以将 SMP 实现为耦合或分离提供程序。 有关这两种类型的提供程序之间的差异,请参阅 WMI MSDN 文档。

分离的提供程序加载并托管在供应商特定的进程中。 此过程通常是始终运行的服务。

启动提供程序可能很耗时,因为它涉及重新加载缓存。 如果 SMP 启动需要多于一秒钟来加载,我们建议实现分离提供程序,以便通过永久性缓存管理存储对象。 此方法有助于提高使用 Windows SM API 管理 SMP 的应用程序的整体性能和响应能力。

Windows SDK 中的解耦Host 示例提供了有关分离提供程序的更多详细信息。

迹象

应用程序开发人员通常想知道对象的状态在更改时何时发生更改。 他们可以通过订阅 WMI 指示来执行此操作。 指示是不同类型的类;它们以异步方式公开,有时从任何管理操作中脱离带外,并且不会保留。 必须支持新的方法,而不是实现熟悉的内部方法(即 EnumerateInstances/GetInstance)。

有四种类型的指示:

  • 到达 – 当将设备或对象实例添加到子系统时,将使用此指示。 例如:将新的物理磁盘添加到子系统,或创建虚拟磁盘。
  • 离开 – 当从子系统中删除设备或对象实例时,将使用此指示。 例如:从子系统中删除物理磁盘或删除存储池。
  • 修改 – 当现有对象上的重要属性发生更改时,将使用此指示。 至少,HealthStatus 和 OperationalStatus 更改必须触发 Modify 指示。 强烈建议更改与对象的操作状态相关的任何其他属性。
  • 警报 – 此指示用于向应用程序发出潜在问题的警报。 目前,唯一定义的警报用于在达到精简预配阈值时通知。

若要实现指示,必须为每个指示类实现两个新的固有方法:

  • EnableIndication – 发出了订阅指示类的请求。 应保存 indicationContext,以便可以在以后的时间点发布指示。
  • DisableIndication – 指示类不再有订阅者。 应进行清理,并且不应再发布此类的指示。 目前,indicationContext 已销毁。

部署

SMP 安装在选定的“管理服务器”上。 这些服务器可以群集以提供冗余。 其他服务器通过 iSCSI 或光纤通道访问分配给它们的存储。 所有这些计算机都可以由从 服务器管理器 运行文件服务器用户界面的服务器管理。

但是,欢迎存储供应商选择最适合其要求的部署模型。

安全模式

SMP 接口支持使用 Windows 安全凭据的单一登录 (SSO) 模型。

在 SSO 模型中,用户一次使用其 Windows 凭据登录到“管理计算机”,并自动获取对其具有访问权限的所有存储资产的访问权限。 无需拥有更多用于存储子系统登录的凭据。

该接口还允许存储管理员管理对单个存储资产的访问控制。 对于每个存储资产,存储管理员可以通过 GetSecurityDescriptor 和 SetSecurityDescriptor 方法向任何 Windows 用户授予不同的访问权限。 因此,与 VDS 模型不同的 SMP 现在可以从任何类型的用户帐户接收请求。

若要实现 SSO 模型,SMP 必须向存储子系统验证 Windows 客户端。 存储子系统必须保留每个存储资产的安全描述符信息。 若要实现身份验证,存储供应商有两种选择:

  • 在子系统中进行身份验证(建议)
  • 在每个 SMP 实例中进行身份验证。

这两个选项都需要在 SMP 和存储子系统之间建立信任关系,以便安全描述符和用户标识信息可以安全地传递。

若要在存储子系统上实现无缝身份验证和授权,我们建议在 SMP 和存储子系统之间建立链接来实现 Kerberos、NTLM 或 SPNego。 如果存储子系统已就位 Web 服务器,则“NTLM over HTTP”协议 [MS-NLMP] 可能更有用。 存储供应商可以选择保留其专有协议来实现 SSO 模型。 但是,不建议使用此方法,因为它可能会导致比实现 Windows 支持的身份验证协议之一更多的工作或设置。

为了支持 Windows 安全策略,存储子系统必须获取用户的“令牌信息”,其中包括用户的安全标识符(SID)和用户所属的任何组的 SID。 如果实现了 Kerberos、NTLM 或 SPNego 协议,则存储子系统将获取用户的令牌信息作为协议的一部分。 如果在 SMP 和存储子系统之间使用了供应商的专有协议,则存储子系统可以通过轻型目录访问协议(LDAP)从 Active Directory 查询用户的令牌信息,并查看用户帐户对象的 tokenGroupsGlobalAndUniversal 属性或 Object-Sid 属性。

使用用户的令牌信息来强制实施 Windows 安全策略,存储子系统需要实现 [MS-DTYP] 中所述的访问检查算法。

如果存储供应商选择不支持此 SSO 模型,则我们建议 SMP 遵循 VDS 中的安全模型 - 仅允许从管理员帐户启动的操作。 但是,此检查现在必须由 SMP 本身执行。