Search code examples
pythonpython-2.7automationansiblecisco

Homemade Ansible Module has "setup error: No module named FabricVcon"


Problem

When testing my own Ansible module, I run into this particular error:"setup error: No module named FabricVcon "
Yet when testing the module's base API (also homemade) in Python, the code works error-free. I'm quite confused because the Ansible Module template I have been using for the other base APIs is the same, just with the relevant information swapped in. All of the previous similar modules work. For clarity's sake, the Ansible module I developed uses the API Python file I developed. That Python API file implements an officially supported Cisco Python library.

Edit: Part of the issue is in the error code itself. The ucsmsdk base class is FabricVCon, while the error tells me it cannot find FabricVcon. The difference is the second C in the name. I have made changes in my code before posting the question to ensure that FabricVCon was used, yet the error persists.

Context

I am currently working on automating Service Profile Template creation and modification via the ucsmsdk in Python with Ansible. I have been contributing to my own forks of the CiscoUcs team's repos of ucsm_apis and ucsm-ansible, using their files as a guide for how my own should work. I have the files from my own repos installed and functioning properly, with the exception of this one. Below I've added the relevant code to the issue, but if you find you need more to work with, please see the two forks on my GitHub account

Code

test_fabric.yml

---
- name: test the test the fabric_vcon_module
  connection: local
  hosts: localhost
  tasks:
  - name: Make the fabric vcon now
    fabric_vcon_module:
      id: "1"
      ls_server_dn: "org-root/org-VM/ls-SCALEIO"
      fabric: "NONE"
      inst_type: "manual"
      placement: "physical"
      select: "all"
      share: "shared"
      transport: "ethernet, fc"
      state: present
      ucs_ip: "IP"
      ucs_username: "USER"
      ucs_password: "PWD"

fabric_vcon.py

(Commented out at the end is my test of the Python code itself)

"""
This module intends on creating higher level api calls for establishing an
 Fabric Vcon
"""
from ucsmsdk.ucsexception import UcsOperationError

def fabric_vcon_create(handle, id, ls_server_dn, fabric='NONE', 
                       inst_type="manual", placement="physical", select="all", 
                       share="shared", transport="ethernet", **kwargs):
    """
    create fabric vcon

    Args:
        handle (UcsHandle)
        id (string): '1' or '2' or '3' or '4'
        ls_server_dn (string):
        fabric (string): 'A' or 'B' or 'any' or 'NONE'
        inst_type (string): 'auto' or 'manual' or 'policy'
        placement (string): 'auto' or 'physical'
        select (string): 'all' or 'assigned-only' or 'dynamic-only' or 
                         'exclude-dynamic' or 'exclude-unassigned' or 
                         'exclude-usnic' or 'unassigned-only' or 'usnic-only'
        share (string): 'different-transport' or 'exclusive-only' or
                        'exclusive-preferred' or 'same-transport' or 'shared'
        transport (string): 

    Returns:
        FabricVCon: managed object

    Raises:
        UcsOperationError: if LsServer is not present

    Example:

    """

    from ucsmsdk.mometa.fabric.FabricVCon import FabricVCon

    obj = handle.query_dn(ls_server_dn)
    if not obj:
        raise UcsOperationError("fabric_vcon_create", "LsServer '%s' does not \
                                  exist" % ls_server_dn)

    mo = FabricVCon(parent_mo_or_dn=obj, id=id, fabric=fabric, 
                    inst_type=inst_type, placement=placement, select=select, 
                    share=share, transport=transport)                  
    mo.set_prop_multiple(**kwargs)
    handle.add_mo(mo, modify_present=True)
    handle.commit()
    return mo

def fabric_vcon_get(handle, id, ls_server_dn, caller="fabric_vcon_get"):
    """
    get fabric vcon

    Args:
        handle (UcsHandle)
        id (string): 
        ls_server_dn (string):
        caller (string):

    Returns:
        FabricVCon: managed object

    Raises:
        UcsOperationError: if the FabricVCon is not present

    Example:

    """
    dn = ls_server_dn + "/vcon-" + id
    mo = handle.query_dn(dn)
    if mo is None:
        raise UcsOperationError(caller, "FabricVCon '%s' does not exist" % dn)
    return mo

def fabric_vcon_exists(handle, id, ls_server_dn, **kwargs):
    """
    checks if fabric vcon exists

    Args:
        handle(UcsHandle)
        id (string): ls server name
        ls_server_dn (string): location to place ls server
        **kwargs: key-value pair of managed object(MO) property and value, Use
                  'print(ucscoreutils.get_meta_info(<classid>).config_props)'
                  to get all configurable properties of class

    Returns:
        (True/False, FabricVCon mo/None)

    Raises:
        None

    Example:

    """

    try:
        mo = fabric_vcon_get(handle=handle, id=id, ls_server_dn=ls_server_dn,
                             caller="fabric_vcon_exists")
    except UcsOperationError:
        return (False, None)

    mo_exists = mo.check_prop_match(**kwargs)
    return (mo_exists, mo if mo_exists else None)

def fabric_vcon_modify(handle, id, ls_server_dn, **kwargs):
    """
    modifies fabric vcon

    Args:
        handle (UcsHandle)
        id (string):
        ls_server_dn (string):
        **kwargs:

    Returns:
        FabricVCon: managed object

    Raises:
        UcsOperationError: if FabricVCon is not present

    Example:

    """
    mo = fabric_vcon_get(handle=handle, id=id, ls_server_dn=ls_server_dn,
                         caller="fabric_vcon_modify")
    mo.set_prop_multiple(**kwargs)
    handle.set_mo(mo)
    handle.commit()
    return mo

def fabric_vcon_delete(handle, id, ls_server_dn):
    """
    deletes fabric vcon

    Args:
        handle (UcsHandle)
        id (String): ls server name
        ls_server_dn (string): ls server's full name

    Returns:
        None

    Raises:
        UcsOperationError: if FabricVCon is not present

    Example:

    """

    mo = fabric_vcon_get(handle=handle, id=id, ls_server_dn=ls_server_dn,
                         caller="fabric_vcon_delete")
    handle.remove_mo()
    handle.commit()
""" 
if __name__ == "__main__":
    from ucsmsdk.ucshandle import UcsHandle

    handle = UcsHandle("10.94.254.136","ansible","elbisna1*")
    handle.login()

    fabric_vcon_create(handle, "4" ,"org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO", fabric='NONE', 
                       inst_type="manual", placement="physical", select="all", 
                       share="shared", transport="ethernet")

    print fabric_vcon_exists(handle, "4" ,"org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO")

    print fabric_vcon_get(handle, "4" ,"org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO")

    fabric_vcon_modify(handle, "4" ,"org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO", inst_type="auto")

    fabric_vcon_delete(handle, "4" ,"org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO")
"""

fabric_vcon_module.py

#!/usr/bin/env python

from ansible.module_utils.basic import *

ANSIBLE_METADATA = {'metadata_version': '1.0',
                    'status': ['preview'],
                    'supported_by': 'community'}



DOCUMENTATION = '''
---
module: cisco_ucs_ls_server
short_description: configures ls server on a cisco ucs server profile
version_added: 0.9.0.0
description:
   -  configures ls server on a cisco ucs server profile
options:
    state:
        description:
         - if C(present), will perform create/add/enable operation
         - if C(absent), will perform delete/remove/disable operation
        required: false
        choices: ['present', 'absent']
        default: "present"
    id:
        version_added: "1.0(1e)"
        description: boot policy name
        required: true
        choices: ['1', '2', '3', '4']
    ls_server_dn:
        version_added: "1.0(1e)"
        description:
        required: false
    fabric:
        version_added: "1.0(1e)"
        description:
        required: false
        choices: ['A', 'B', 'any', 'NONE']
    inst_type:
        version_added: "1.0(1e)"
        description:
        required: false
        choices: ['auto', 'manual', 'policy']
    placement:
        version_added: "1.0(1e)"
        description:
        required: false
        choices: ['auto', 'physical']
    select:
        version_added: "1.0(1e)"
        description:
        required: false
        choices: ['all', 'assigned-only', 'dynamic-only', 'exclude-dynamic', 'exclude-unassigned',
                  'exclude-usnic', 'unassigned-only', 'usnic-only']
    share:
        version_added: "1.0(1e)"
        description:
        required: false
        choices: ['different-transport', 'exclusive-only', 'exclusive-preferred', 'same-transport', 'shared']
    transport:
        version_added: "1.0(1e)"
        description:
        required: false
requirements: ['ucsmsdk', 'ucsm_apis']
author: "Cisco Systems Inc(ucs-python@cisco.com)"
'''


EXAMPLES = '''
- name:
  fabric_vcon_module:
    id: "1"
    ls_server_dn: "org-root/ls-spt-test"
    fabric: "B"
    inst_type: "manual"
    select: "assigned-only"
    share: "different-transport"
    state: "present"
    ucs_ip: "192.168.1.1"
    ucs_username: "admin"
    ucs_password: "password"
'''

#Arguments object for the Managed Object in question
def _argument_mo():
    return dict(
                id=dict(required=True, type='str', choices=['1', '2', '3', '4']),
                ls_server_dn=dict(required=True, type='str'),
                fabric=dict(type='str', choices=['A', 'B', 'any', 'NONE'], default="NONE"),
                inst_type=dict(type='str', choices=['auto', 'manual', 'policy'], default="manual"),
                placement=dict(type='str', choices=['auto', 'physical'], default="physical"),
                select=dict(type='str', choices=['all', 'assigned-only', 'dynamic-only', 'exclude-dynamic',
                            'exclude-unassigned', 'exclude-usnic', 'unassigned-only', 'usnic-only'],
                            default="all"),
                share=dict(type='str', choices=['different-transport', 'exclusive-only', 
                                'exclusive-preferred', 'same-transport', 'shared'], default="shared"),
                transport=dict(type='str', default="ethernet")
    )

#Arguments object unique to the Ansible Module
def _argument_custom():
    return dict(
        state=dict(default="present",
                   choices=['present', 'absent'],
                   type='str'),
    )

#Arguments object related to the UcsHandle
def _argument_connection():
    return  dict(
        # UcsHandle
        ucs_server=dict(type='dict'),

        # Ucs server credentials
        ucs_ip=dict(type='str'),
        ucs_username=dict(default="admin", type='str'),
        ucs_password=dict(type='str', no_log=True),
        ucs_port=dict(default=None),
        ucs_secure=dict(default=None),
        ucs_proxy=dict(default=None)
    )


#Creates the AnsibleModule object with the all arguments
def _ansible_module_create():
    argument_spec = dict()
    argument_spec.update(_argument_connection())
    argument_spec.update(_argument_mo())
    argument_spec.update(_argument_custom())

    return AnsibleModule(argument_spec,
                         supports_check_mode=True)


#Retrieves non-None mo properties
def _get_mo_params(params):
    from ansible.module_utils.cisco_ucs import UcsConnection
    args = {}
    for key in _argument_mo():
        if params.get(key) is None:
            continue
        args[key] = params.get(key)
    return args


def setup_fabric_vcon(server, module):
    from ucsm_apis.service_profile.fabric_vcon import fabric_vcon_create
    from ucsm_apis.service_profile.fabric_vcon import fabric_vcon_exists
    from ucsm_apis.service_profile.fabric_vcon import fabric_vcon_delete

    ansible = module.params
    args_mo  =  _get_mo_params(ansible)
    exists, mo = fabric_vcon_exists(handle=server, **args_mo)

    if ansible["state"] == "present":
        if module.check_mode or exists:
            return not exists
        fabric_vcon_create(handle=server, **args_mo)
    else:
        if module.check_mode or not exists:
            return exists
        fabric_vcon_delete(server, mo.name, args_mo['org_dn'])

    return True

#Attempts to run the above method and provides error handling if it fails
def setup(server, module):
    result = {}
    err = False

    try:
        result["changed"] = setup_fabric_vcon(server, module)
    except Exception as e:
        err = True
        result["msg"] = "setup error: %s " % str(e)
        result["changed"] = False

    return result, err

#Creates the module and makes the connections, only real work is done in setup
def main():
    from ansible.module_utils.cisco_ucs import UcsConnection

    module = _ansible_module_create()
    conn = UcsConnection(module)
    server = conn.login()
    result, err = setup(server, module)
    conn.logout()
    if err:
        module.fail_json(**result)
    module.exit_json(**result)


if __name__ == '__main__':
    main()

Solution

  • The Solution

    It turns out that Ansible was looking in /~/.local/lib/python2.7/site-packages/ucsm_apis-0.9.0.0-py2.7.egg/ucsm_apis for the ucsm_api files rather than /usr/lib/python2.7/site-packages/ucsm_apis-0.9.0.0-py2.7.egg/ucsm_apis first.

    Deleting the local version and copying over from the machine version resolved the issue.

    How I Discovered it

    I embedded the line import pdb;pdb.set_trace() in the setup_fabric_vcon(server, module) function of the fabric_vcon_module file, just after the import statements.

    Running python /[proper_path]/fabric_vcon_module.py /[proper_path]/args_fabric.json and moving line by line in pdb showed that the error came from the fabric_vcon_create(handle=server, **args_mo) line.

    I reran pdb to step into the function and the calls to the function showed me the file location of the library being accessed.

    The JSON File

    {
        "ANSIBLE_MODULE_ARGS": {
          "id": "1",
          "ls_server_dn": "org-root/org-DHS-VM/ls-DS-ESX-C2-SCALEIO",
          "fabric": "NONE",
          "inst_type": "manual",
          "placement": "physical",
          "select": "all",
          "share": "shared",
          "transport": "ethernet, fc",
          "state": "present",
          "ucs_ip": "10.94.254.136",
          "ucs_username": "ansible",
          "ucs_password": "elbisna1*"
        }
    }