{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Pywbem_mock Demonstration Notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Table of contents for this notebook\n",
    "\n",
    "- [Table of Contents for this notebook](#Table-of-Contents-for-this-notebook)\n",
    "- [Using pywbem_mock to create a simple repository](#Using-pywbem_mock-to-create-a-simple-repository)\n",
    "    - [Create a FakedWBEMConnection and repository](#Create-a-FakedWBEMConnection-and-repository)\n",
    "    - [Display the created repository](#Display-the-created-repository)\n",
    "    - [Execute WBEM operations on the mock repository](#Execute-WBEM-operations-on-the-mock-repository)\n",
    "        - [Create a new instance of the defined class](#Create-a-new-instance-of-the-defined-class)\n",
    "        - [Retrieve all instances of the class](#Retrieve-all-instances-of-the-class)\n",
    "        - [Retrieve the new instance from the mock server](#Retrieve-the-new-instance-from-the-mock-server)\n",
    "        - [Get the new instance with server defined path](#Get-the-new-instance-with-server-defined-path)\n",
    "        - [Retrieve all instances of the class](#Retrieve-all-instances-of-the-class)\n",
    "        - [Delete the new instance](#Delete-the-new-instance)\n",
    "        - [Executing a CIM method](#Executing-a-CIM-method)\n",
    "- [Creating a repository from DMTF Schema](#Creating-a-repository-from-DMTF-Schema)\n",
    "    - [Add  more classes to this repository](#Add-more-classes-to-this-repository)\n",
    "    - [Add more CIM objects](#Add-more-CIM-objects)\n",
    "    - [The association operations](#The-association-operations)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The pywbem client includes a package (`pywbem_mock`) to mock a WBEM server and allow the pywbem client to \n",
    "execute WBEM operations against this fake WBEM server.\n",
    "\n",
    "This notebook contains examples of creating and using a simple repository and creating and using more complex repositories using the DMTF released Schema MOF files.\n",
    "\n",
    "For more detailed information on the package and its APIs see the pywbem client documentation on \n",
    "[read-the-docs](https://pywbem.readthedocs.io/en/stable/mocksupport.html) which includes both an overview and API descriptions for the `pywbem_mock` package of pywbem."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using pywbem_mock to create a simple repository\n",
    "\n",
    "The pywbem mock package (`pywbem_mock`) allows a user of the pywbem client to mock a WBEM server so that pywbem WBEM request methods can be executed without having a WBEM server available;\n",
    "\n",
    "The pywbem mock support consists of the `pywbem_mock.FakedWBEMConnection` class that establishes a faked connection. That class is a subclass of `pywbem.WBEMConnection` and replaces its internal methods that use HTTP/HTTPS to communicate with a WBEM server with methods that operate on a local in-memory repository of CIM objects (the mock repository).\n",
    "\n",
    "As a result, the operation methods of `FakedWBEMConnection` are those inherited from WBEMConnection, so they have the exact same input parameters, output parameters, return values, and most of the server raised exceptions, as when being invoked on a WBEMConnection object against a WBEM server.\n",
    "\n",
    "Each `FakedWBEMConnection` object has its own mock repository. The mock repository contains the same kinds of CIM objects a WBEM server repository contains: CIM classes, CIM instances, and CIM qualifier types (declarations), all contained in CIM namespaces.\n",
    "\n",
    "Because `FakedWBEMConnection` operates only on the mock repository, the class does not have any connection-related or security-related constructor parameters.\n",
    "\n",
    "`FakedWBEMConnection` has methods that allow the user to add CIM classes, instances and qualifier types to its mock repository and view what is in the mock repository either by defined pywbem CIM objects (CIMClass, CIMInstance, etc. or by compiling MOF files containing the definition of CIM objects.  The methods include:\n",
    "\n",
    "-  `compile_mof_file()` - Compiles a MOF file into the fake repository\n",
    "-  `compile_mof_string()` - Compiles a string containing MOF into the fake repository \n",
    "-  `add_cimobjects()` - Inserts CIM bbjects defined with pywbem classes (CIMClass, CIMInstance, etc.) into the defined fake repository.\n",
    "-  `add_namespace()` - Defines a CIM namespace in the fake repository\n",
    "-  `display_repository()` - Display the namespaces, CIM classes, CIM instances, etc. that are in the Fake repository\n",
    "\n",
    "CIM instances in the repository can be modified or deleted by using the pywbem `WBEMConnection` operation methods such as `DeleteClass()` or `ModifyInstance()`. Classes and qualifier declarations can be deleted using the corresponding delete methods.\n",
    "\n",
    "The following cells demonstrate creating a FakedWBEMConnection, populating it repository, accessing the objects created in the repository and deleting the CIMInstances created.\n",
    "\n",
    "It also demonstrates defining and access a CIM method in the fake repository."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a FakedWBEMConnection and repository\n",
    "\n",
    "The following code demonstrates adding a simple set of qualifier declarations and a CIM class to the Fake repository by compiling their MOF definitions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pywbem\n",
    "import pywbem_mock\n",
    "\n",
    "# MOF string defining qualifiers declarations, class, and instance\n",
    "# This mof will be used throughout this notebook\n",
    "mof = '''\n",
    "    Qualifier Key : boolean = false,\n",
    "        Scope(property, reference),\n",
    "        Flavor(DisableOverride, ToSubclass);\n",
    "    Qualifier Description : string = null,\n",
    "        Scope(any),\n",
    "        Flavor(EnableOverride, ToSubclass, Translatable);    \n",
    "    Qualifier In : boolean = true, \n",
    "        Scope(parameter), \n",
    "        Flavor(DisableOverride, ToSubclass);\n",
    "    Qualifier OUT : boolean = true, \n",
    "        Scope(parameter), \n",
    "        Flavor(DisableOverride, ToSubclass);\n",
    "\n",
    "         [Description (\"A dumb test class\")]\n",
    "    class CIM_Foo {\n",
    "    \n",
    "            [Key, Description (\"The key property\")]\n",
    "        string InstanceID;\n",
    "        \n",
    "            [Description (\"Some simplistic data\")]            \n",
    "        uint32 SomeData;\n",
    "        \n",
    "            [Description (\"A method with parameters.\"   \n",
    "                          \"uint16 as return value\")]                          \n",
    "        uint16 Method1(\n",
    "          [IN, Description(\"Optional Input String Param\")]\n",
    "            string InParam,\n",
    "          // declare IN (false) because default is True  \n",
    "          [IN(false), OUT, Description(\"Optional Out Param\")]\n",
    "            uint32 OutParam);\n",
    "    };\n",
    "\n",
    "    // Create an instance of CIM_Foo\n",
    "    instance of CIM_Foo { InstanceID = \"I1\"; SomeData=3; };\n",
    "    '''\n",
    "\n",
    "# Create a faked connection (with a mock repository in full mode)\n",
    "conn = pywbem_mock.FakedWBEMConnection(default_namespace='root/cimv2')\n",
    "\n",
    "# Compile the MOF string and add its CIM objects to the default namespace\n",
    "# of the mock repository\n",
    "conn.compile_mof_string(mof)\n",
    "\n",
    "print(\"Qualifier declarations and classes installed in Fake repository\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Display the created repository\n",
    "\n",
    "At any time, the data in the repository can be displayed using the method `FakedWBEMConnection.display_repository()`.\n",
    "\n",
    "This method includes a number of options to display selected namespaces, define the destination for the output, display only a summary of data in the repository, and define the output format for the displayed objects (mof, xml, repr)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "conn.display_repository()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Execute WBEM operations on the mock repository\n",
    "\n",
    "Once qualifier declarations, classes, and instances have been inserted into  the mock repository they\n",
    "can be retrieved using the `WBEMConnection` methods provided by `pywbem`.\n",
    "\n",
    "Thus, `WBEMConnection.getQualifier()` retrieves a single qualifier declaration. The method `tomof()` is\n",
    "a method in pywbem cimobject classes CIMQualifierDeclaration, CIMClass, and CIMInstance and is an easy\n",
    "way to display the objects returned from the repository in the standard DMTF language for CIM objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pywbem import CIMInstanceName, Error\n",
    "## Perform operations on the faked connection:\n",
    "\n",
    "# Enumerate top-level classes in the default namespace (without subclasses)\n",
    "classes = conn.EnumerateClasses();\n",
    "\n",
    "### Get the 'Description' qualifier type in the default namespace\n",
    "qd = conn.GetQualifier('Description')\n",
    "print(qd.tomof())\n",
    "\n",
    "### Enumerate subclasses of 'CIM_Foo' in the default namespace (without subclasses)\n",
    "classes = conn.EnumerateClasses(ClassName='CIM_Foo')\n",
    "for cls in classes:\n",
    "    print(cls.tomof())\n",
    "\n",
    "### Get 'CIM_Foo' class in the default namespace\n",
    "my_class = conn.GetClass('CIM_Foo')\n",
    "\n",
    "### Get a specific instance of 'CIM_Foo' in the default namespace\n",
    "keybindings = {'InstanceID': \"I1\"}\n",
    "inst = conn.GetInstance(CIMInstanceName('CIM_Foo', keybindings))\n",
    "print(\"RESULTS:\")\n",
    "print(inst.tomof())\n",
    "### print the path of the returned instance\n",
    "print(\"path:{0}\".format(inst.path))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a new instance of the defined class\n",
    "\n",
    "Creating an instance primarily involves attaching properties with their name, value (and often type)\n",
    "as a dictionary to a CIMInstance with the  name of the class for the new instance. The method\n",
    "`WBEMConnection.CreateInstance()` only requires the new instance object (and the namespace if the default\n",
    "namespace is not being used) to manage the instance that is being created.\n",
    "\n",
    "The mocker creates the path for this instance and inserts the new instance into the mock\n",
    "repository. Note that a successful `CreateInstance()` returns a CIMInstanceName for the new instance which can be used to access the new instance on the WBEM server."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pywbem import CIMInstance, Uint32\n",
    "\n",
    "p = {\"InstanceID\": \"I2\", \"SomeData\": Uint32(999)}\n",
    "\n",
    "newinst = CIMInstance(\"CIM_Foo\", properties=p)\n",
    "new_path = None\n",
    "try:\n",
    "    new_path = conn.CreateInstance(newinst)\n",
    "    print(\"Return path: %s\" % new_path)\n",
    "except Error as er:\n",
    "    print(\"Exception on CreateInstance. exception=%s\" % er)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve the new instance from the mock server\n",
    "\n",
    "Here we retrieve the instance we just created from the existing connection and display the instance using the tomof() method.\n",
    "\n",
    "Note that we are retrieving the instance using the path we previously defined. Since the server returns the path of an instance it creates, that is the path object your should be using to retrieve an instance. Under some circumstances the server could change the path and return a path different that what eyou defined. The mocker does not change the path provided."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "myinst = conn.GetInstance(CIMInstanceName('CIM_Foo', keybindings={'InstanceID': \"I2\"}))\n",
    "\n",
    "print(\"path:%s\\n%s\" % (myinst.path, myinst.tomof()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Get the new instance with server defined path\n",
    "\n",
    "Since `WBEMConnection.CreateInstance(` returned the path the server created for the instance we inserted into the repository, that path can also be used to retrieve the instance from the repository"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Path to new instance %s' % new_path)\n",
    "myinst2 = conn.GetInstance(new_path)\n",
    "print(myinst2.tomof())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieve all instances of the class\n",
    "\n",
    "In this case we display the returned instances using the string representation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "insts = conn.EnumerateInstances(\"CIM_Foo\")\n",
    "for inst in insts:\n",
    "    print(\"path=%s\" % inst.path)\n",
    "    print(\"%s\" % inst)\n",
    " "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete the new instance\n",
    "\n",
    "The WBEMConnection::DeleteInstance method deletes a single instance of an instanced given the instance path.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    conn.DeleteInstance(new_path)\n",
    "    for inst in insts:\n",
    "        print(\"path=%s\" % inst.path)\n",
    "except Error as er:\n",
    "    print(\"Error with delete\")\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining, registering, and executing a mock CIM method\n",
    "\n",
    "The mocker can execute CIM methods as if they were on a server.  However, since there are no real providers\n",
    "in the mocker, the user must define what a method would do in the server.  The user-defined provider is defined as a subclass of the MethodProvider class defined in pywbem_mock. This subclass defines\"\n",
    "  * The class(es) for which this provider will be called:\n",
    "  * a __init__ method to make the CIM repository avaiable.\n",
    "  * The InvokeMethod method that will be executed.\n",
    "  \n",
    " This sublcas is provided to the mocker by creating a user-defined provider and registering that provider using the register_provider method.\n",
    " \n",
    " The following example defines the user-defined provider (CIM_Foo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "from pywbem import CIMParameter\n",
    "from pywbem_mock import MethodProvider\n",
    "import six\n",
    "\n",
    "# Definition of the user-defined method provider.  This capability is\n",
    "# new in pywbem 1.0.0. Earlier versions used a callback functionalirty\n",
    "# that was ver limited.\n",
    "class TST_ClassProvider(MethodProvider):\n",
    "    \"\"\"\n",
    "    User test provider for InvokeMethod using TST_Class and method1.\n",
    "    \"\"\"\n",
    "    # Names of the class(es) for which this provider is to be registered\n",
    "    provider_classnames = 'TST_Class'\n",
    "\n",
    "    def __init__(self, cimrepository):\n",
    "        super(TST_ClassProvider, self).__init__(cimrepository)\n",
    "\n",
    "    def InvokeMethod(self, namespace, MethodName, ObjectName, Params):\n",
    "        \"\"\"\n",
    "        Simplistic test method withn TST_Class.\n",
    "        \"\"\"\n",
    "        # Basic validity tests including \n",
    "        # * valid namespace,\n",
    "        # * validity of the ObjectName type, \n",
    "        # * existence of the ObjectName in the repository\n",
    "        # * existence of the MethodName in the class defined by the ObjectName \n",
    "        # have already be completed before this method is called.  \n",
    "        # The user method need only:\n",
    "        # * process input parameters\n",
    "        # * execute any provider specific code (what ever the method was to do)\n",
    "        # * create any output parameters\n",
    "        # * create a return value\n",
    "        # * return the return_value and return parameters\n",
    "        \n",
    "        print('Input Parameters {}'.format(Params))\n",
    "\n",
    "        # Execute provider specific code and define any return parameters and\n",
    "        # a return value\n",
    "        out_params = [CIMParameter('OutputParam1', 'string', value=3)]\n",
    "        return_params = out_params\n",
    "        return_value = 0\n",
    "\n",
    "        return (return_value, return_params)\n",
    "\n",
    "class_with_method_mof = '''\n",
    "    Qualifier Out : boolean = false,\n",
    "        Scope(parameter),\n",
    "        Flavor(DisableOverride, ToSubclass);\n",
    "\n",
    "    Qualifier Static : boolean = false, \n",
    "        Scope(property, method), \n",
    "        Flavor(DisableOverride, ToSubclass);\n",
    "\n",
    "    class TST_Class {\n",
    "\n",
    "        string InstanceID;\n",
    "\n",
    "          [Static,\n",
    "           Description(\"Static method with input and output parameters\")]\n",
    "        uint32 Method1(\n",
    "            [IN, Description(\"Input param 1\")]\n",
    "          string InputParam1,\n",
    "            [IN (False), OUT, Description(\"Output param 1\")]\n",
    "          string OutputParam1);\n",
    "    };\n",
    "'''\n",
    "\n",
    "# Compile the MOF string and add its CIM objects to the default namespace\n",
    "# of the mock repository\n",
    "conn.compile_mof_string(class_with_method_mof)\n",
    "\n",
    "# Register the method provider defined above, for the\n",
    "# default namespace of the connection\n",
    "\n",
    "# Try block allows excuting cell multiple times without exception\n",
    "# because the provider is already registered on subsequent calls.\n",
    "try:\n",
    "    conn.register_provider(TST_ClassProvider(conn.cimrepository),\n",
    "                           namespaces=None,\n",
    "                           schema_pragma_files=None, verbose=None)\n",
    "except Exception as ve:\n",
    "    print(ve)\n",
    "    \n",
    "# display the user providers registered\n",
    "print(\"Display of registered providers\")\n",
    "conn.display_registered_providers()\n",
    "print(\"===============\")\n",
    "\n",
    "# Define a value for the Method Parameter InpurParam1\n",
    "print(\"Execute the user provider method:\\n\")\n",
    "params = [('InputParam1', 'someData')]\n",
    "# Invoke static method Method1\n",
    "result = conn.InvokeMethod('Method1', 'TST_Class', Params=params)\n",
    "\n",
    "print('\\nReturn value: %r' % result[0])\n",
    "print('Output parameters: %r' % (result[1],))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating a repository from DMTF Schema\n",
    "\n",
    "In addition to creating a repository with specifically defined classes, qualifier declarations and instances the repository can be created using the published DMTF schema as the source for qualifier declarations and classes. The DMTF schema is released on a regular basis with new functionality, fixes, etc. And is published on the DMTF web site. It includes the set of CIM qualifier declarations defined by the DMTF and the CIM classes that make up the CIM model. It is available in both MOF and XML formats. The method documented here uses the MOF files as the basis for creating CIM qualifier declarations and CIM classes in the mock repository.\n",
    "\n",
    "Using this method requires that you make the following decisions:\n",
    "\n",
    "-  Which version of the published DMTF released schema you intend to use?  The available Schema versions are listed on the [DMTF web site](https://www.dmtf.org/standards/cim)\n",
    "\n",
    "-  Where in your local environment do you want to save and expand the schema. This should be a directory and serves both as the storage space for download and expansion and the location checked before download so that the schema is not downloaded each time it is used?\n",
    "\n",
    "-  Use of the experimental schema for the defined DMTF schema release as well as the released schema.\n",
    "\n",
    "-  The leaf classes in the class hiearchy you want to mock. The method takes responsibility for determining superclasses and other required classes ( ex. associations, embedded instances) defined in the MOF for any of the leaf classes you specify.\n",
    "\n",
    "The method `FakedWBEMConnection.compile_dmtf_schema()` performs a number of functions including:\n",
    "\n",
    "- Downloading the DMTF Schema defined by the version if it is not already downloaded.\n",
    "- Creating a list of all classes that must be compiled based on the list of classes provided (the leaf classes required for the mock)\n",
    "- Compiling all of the qualifier declarations defined in the CIM Schema.\n",
    "- Compiling the defined classes and classes on which they depend into the repository.\n",
    "\n",
    "The following example defines a DMTF schema version, the destination directory, and the leaf classes of interest and then calls the compile_dmtf_schema method to create a mock repository. At the end of the operation, the mock repository is includes the complete DMTF set of qualifier declarations (there are only about 100 of them) and the classes you specified and their dependencies. The destination directory should also include the complete download from the DMTF and the expanded mof files for the qualifier declarations and classes.\n",
    "\n",
    "Note: that each time you call compile_dmtf_schema() it installs the complete set of qualifier declarations in the mock repository so that it is best to create a complete list of leaf classes required an call this method only once.\n",
    "\n",
    "*Warning*: Some of these cells create new objects on the server. Once those objects are created, they cannot simply be recreated on the server so the repeated execution of some of the cells without restarting (see Kernel in the menu) will cause exception returns from the server.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pywbem\n",
    "import pywbem_mock\n",
    "\n",
    "import os\n",
    "print(os.getcwd())\n",
    "\n",
    "# Defines the version of the DMTF schema to be installed.\n",
    "# This demo is done with the schema already loaded into the pywbem development environment assuming that\n",
    "# the user is using the docs/notebooks directory. This may change as pywbem updates the schema use\n",
    "# in out tests\n",
    "DMTF_TEST_SCHEMA_VER = (2, 49, 0)\n",
    "# location of schema 2.49.0 in pywbem 0.13.0 development\n",
    "# code cloed from github\n",
    "SCHEMA_DIR = os.path.join('..', '..', 'tests', 'schema')\n",
    "print(SCHEMA_DIR)\n",
    "\n",
    "# An alternative would be to define your own schema and schema directory\n",
    "# The schema will be downloaded and installed on the first use.\n",
    "#DMTF_TEST_SCHEMA_VER = \"2.51.0\"\n",
    "#SCHEMA_DIR = \".\"\n",
    "\n",
    "classnames = ['CIM_RegisteredProfile', 'CIM_Namespace', 'CIM_ObjectManager',\n",
    "              'CIM_ElementConformsToProfile', 'CIM_ReferencedProfile']\n",
    "\n",
    "conn = pywbem_mock.FakedWBEMConnection(default_namespace='root/cimv2')\n",
    "print(conn)\n",
    "\n",
    "# The following methods were added in pywbem 1.0.0. To execute this code\n",
    "# with earlier versions of pywbem replace the two methods with the following\n",
    "# which was removed in pywbem 1.0/0\n",
    "#\n",
    "#    conn.compile_dmtf_schema(DMTF_TEST_SCHEMA_VER, SCHEMA_DIR,\n",
    "#                                 class_names=classnames, verbose=False)\n",
    "\n",
    "schema = pywbem_mock.DMTFCIMSchema(DMTF_TEST_SCHEMA_VER, SCHEMA_DIR,\n",
    "                                   verbose=True)\n",
    "\n",
    "conn.compile_schema_classes(classnames,\n",
    "                            schema.schema_pragma_file,\n",
    "                            verbose=True)\n",
    "\n",
    "conn.display_repository()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add  more classes to this repository\n",
    "\n",
    "Subsequent to the installation of the DMTF schema we can add classes of our own to the repository.\n",
    "This adds two dummy classes that really do not depend on the schema and simply demonstrates installing classes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mof= '''\n",
    "    class TST_Class1 {\n",
    "          [Key]\n",
    "        string InstanceID;\n",
    "        string Prop1;\n",
    "    };\n",
    "\n",
    "    class TST_Class2 {\n",
    "          [Key]\n",
    "        string InstanceID;\n",
    "        string Prop2;\n",
    "    };\n",
    "\n",
    "      [Association]\n",
    "    class TST_Association12 {\n",
    "          [Key]\n",
    "        TST_Class1 REF Ref1;\n",
    "          [Key]\n",
    "        TST_Class2 REF Ref2;\n",
    "    };\n",
    "'''\n",
    "\n",
    "conn.compile_mof_string(mof)\n",
    "# print the class returned from the server using the __str__ magic method and __repr__ magic method\n",
    "print(conn.GetClass(\"TST_Class1\"))\n",
    "print(repr(conn.GetClass(\"TST_Class2\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add more CIM objects\n",
    "\n",
    "Now that we have classes in the repository we can add some CIM Instances. The following cell defines an instance of each of the new classes and also an assoiation instance that relates the instances of the classes.\n",
    "\n",
    "In this case, they are defined as instances of pywbem objects to demonstrate adding pywbem CIM objects to the repository.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ns = conn.default_namespace\n",
    "\n",
    "# Define a key for this instance\n",
    "c1_key = pywbem.CIMProperty('InstanceID', type='string', value='111')\n",
    "\n",
    "# Create the path for the instance (its CIMInstanceName)\n",
    "c1_path = pywbem.CIMInstanceName(\n",
    "    'TST_Class1',\n",
    "    keybindings={c1_key.name: c1_key.value},\n",
    "    namespace=ns\n",
    ")\n",
    "\n",
    "# Create a CIMInstance of class TST_Class1\n",
    "c1 = pywbem.CIMInstance(\n",
    "    'TST_Class1',\n",
    "    properties=[\n",
    "        c1_key,\n",
    "        pywbem.CIMProperty('Prop1', type='string', value='1'),\n",
    "    ],\n",
    "    path=c1_path,\n",
    ")\n",
    "\n",
    "# Create a second instance\n",
    "c2_key = pywbem.CIMProperty('InstanceID', type='string', value='222')\n",
    "c2_path = pywbem.CIMInstanceName(\n",
    "    'TST_Class2',\n",
    "    keybindings={c2_key.name: c2_key.value},\n",
    "    namespace=ns\n",
    ")\n",
    "c2 = pywbem.CIMInstance(\n",
    "    'TST_Class2',\n",
    "    properties=[\n",
    "        c2_key,\n",
    "        pywbem.CIMProperty('Prop2', type='string', value='2'),\n",
    "    ],\n",
    "    path=c2_path,\n",
    ")\n",
    "\n",
    "# Create keys and paths for CIMInstance 1 and 2\n",
    "a12_key1 = pywbem.CIMProperty('Ref1', type='reference', value=c1_path)\n",
    "a12_key2 = pywbem.CIMProperty('Ref2', type='reference', value=c2_path)\n",
    "a12_path = pywbem.CIMInstanceName(\n",
    "    'TST_Association12',\n",
    "    keybindings={\n",
    "        a12_key1.name: a12_key1.value,\n",
    "        a12_key2.name: a12_key2.value,\n",
    "    },\n",
    ")\n",
    "\n",
    "# Define the association instance\n",
    "a12 = pywbem.CIMInstance(\n",
    "    'TST_Association12',\n",
    "    properties=[\n",
    "        a12_key1,\n",
    "        a12_key2,\n",
    "    ],\n",
    "    path=a12_path,\n",
    ")\n",
    "\n",
    "# add all of the created CIM instances to the repository\n",
    "conn.add_cimobjects([c1, c2, a12])\n",
    "\n",
    "# Get the instances from the repository and display them\n",
    "returned_instances = conn.EnumerateInstances('TST_Class2')\n",
    "for inst in returned_instances:\n",
    "    print(inst.tomof())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The association operations\n",
    "\n",
    "The relation between a source instance and it associated instances is defined with instances of an association class (CIMClass with Association qualifier) and can be accessed  through the `References()` and `Associators()` WBEMConnection methods. These are powerful methods that provide the basis for navigating through the CIM model on a server.\n",
    "\n",
    "Note that in most cases an association can also be accessed directly by executing GetInstance or EnumerateInstances on the Association class. However, the power of the `References()` and `Associators()` methods is that the return instances within the class hiearchy defined by association instances including optionally accounting for subclasses, filtering of the roles (Reference property names) and the Association class name.\n",
    "\n",
    "Pywbem_mock accesses associations defined in the mock repository with the `WBEMConnection.Associators()` and `WBEMConnection.References()` methods and implements the same retrieval algorithms as a WBEM server to get\n",
    "the correct instances based on the request input parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Display the instances of the test association.\n",
    "returned_assoc_insts = conn.EnumerateInstances(\"TST_Association12\")\n",
    "for inst in returned_assoc_insts:\n",
    "    print(inst.tomof())\n",
    "\n",
    "# Get the References for each of the target instances defined for\n",
    "# the association.\n",
    "cls_111_paths = conn.EnumerateInstanceNames('TST_Class1')\n",
    "for path in cls_111_paths:\n",
    "    returned_refs = conn.References(path)\n",
    "    print('Refs for %s' % path)\n",
    "    for inst in returned_refs:\n",
    "        print(inst.tomof())\n",
    "        \n",
    "# Get the associated instances for the target instances using the\n",
    "# Associators operation\n",
    "for path in cls_111_paths:\n",
    "    returned_assocs = conn.Associators(path)\n",
    "    print('Associations for %s' % path)\n",
    "    for inst in returned_assocs:\n",
    "        print(inst.tomof())\n",
    "    for inst in returned_assocs:\n",
    "        print('Returned association path: %s:' % inst.path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
