|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
| 5 | + |
| 6 | +from __future__ import absolute_import, division, print_function |
| 7 | +__metaclass__ = type |
| 8 | + |
| 9 | +ANSIBLE_METADATA = {'metadata_version': '1.1', |
| 10 | + 'status': ['preview'], |
| 11 | + 'supported_by': 'community'} |
| 12 | + |
| 13 | +DOCUMENTATION = r''' |
| 14 | +--- |
| 15 | +module: ucs_service_profile_association |
| 16 | +short_description: Configures Service Profile Association on Cisco UCS Manager |
| 17 | +description: |
| 18 | +- Configures Service Profile Association (change association or disassociate) on Cisco UCS Manager. |
| 19 | +extends_documentation_fragment: cisco.ucs.ucs |
| 20 | +options: |
| 21 | + state: |
| 22 | + description: |
| 23 | + - If C(present), will verify service profile association and associate with specified server or server pool if needed. |
| 24 | + - If C(absent), will verify service profile is not associated and will disassociate if needed. This is the same as specifying Assign Later in the webUI. |
| 25 | + choices: [present, absent] |
| 26 | + default: present |
| 27 | + service_profile_name: |
| 28 | + description: |
| 29 | + - The name of the Service Profile being associated or disassociated. |
| 30 | + required: yes |
| 31 | + server_assignment: |
| 32 | + description: |
| 33 | + - "Specifies how to associate servers with this service profile using the following choices:" |
| 34 | + - "server - Use to pre-provision a slot or select an existing server. Slot or server is specified by the server_dn option." |
| 35 | + - "pool - Use to select from a server pool. The server_pool option specifies the name of the server pool to use." |
| 36 | + - Option is not valid if the service profile is bound to a template. |
| 37 | + - Optional if the state is absent. |
| 38 | + choices: [server, pool] |
| 39 | + required: yes |
| 40 | + server_dn: |
| 41 | + description: |
| 42 | + - The Distinguished Name (dn) of the server object used for pre-provisioning or selecting an existing server. |
| 43 | + - Required if the server_assignment option is server. |
| 44 | + - Optional if the state is absent. |
| 45 | + server_pool_name: |
| 46 | + description: |
| 47 | + - Name of the server pool used for server pool based assignment. |
| 48 | + - Required if the server_assignment option is pool. |
| 49 | + - Optional if the state is absent. |
| 50 | + restrict_migration: |
| 51 | + description: |
| 52 | + - Restricts the migration of the service profile after it has been associated with a server. |
| 53 | + - If set to no, Cisco UCS Manager does not perform any compatibility checks on the new server before migrating the existing service profile. |
| 54 | + - If set to no and the hardware of both servers used in migration are not similar, the association might fail. |
| 55 | + choices: ['yes', 'no'] |
| 56 | + default: 'no' |
| 57 | + org_dn: |
| 58 | + description: |
| 59 | + - The distinguished name (dn) of the organization where the resource is assigned. |
| 60 | + default: org-root |
| 61 | +requirements: |
| 62 | +- ucsmsdk |
| 63 | +author: |
| 64 | +- David Soper (@dsoper2) |
| 65 | +- CiscoUcs (@CiscoUcs) |
| 66 | +version_added: 2.10 |
| 67 | +''' |
| 68 | + |
| 69 | +EXAMPLES = r''' |
| 70 | +- name: Change Service Profile Association to server pool Container-Pool and restrict migration |
| 71 | + cisco.ucs.ucs_service_profile_association: |
| 72 | + hostname: 172.16.143.150 |
| 73 | + username: admin |
| 74 | + password: password |
| 75 | + service_profile_name: test-sp |
| 76 | + server_assignment: pool |
| 77 | + server_pool_name: Container-Pool |
| 78 | + restrict_migration: 'yes' |
| 79 | +
|
| 80 | +- name: Attempt to change association once a minute for up to 10 minutes |
| 81 | + cisco.ucs.ucs_service_profile_association: |
| 82 | + hostname: 172.16.143.150 |
| 83 | + username: admin |
| 84 | + password: password |
| 85 | + service_profile_name: test-sp |
| 86 | + server_assignment: server |
| 87 | + server_dn: sys/chassis-2/blade-1 |
| 88 | + register: result |
| 89 | + until: result.assign_state == 'assigned' and result.assoc_state == 'associated' |
| 90 | + retries: 10 |
| 91 | + delay: 60 |
| 92 | +
|
| 93 | +- name: Disassociate Service Profile |
| 94 | + cisco.ucs.ucs_service_profile_association: |
| 95 | + hostname: 172.16.143.150 |
| 96 | + username: admin |
| 97 | + password: password |
| 98 | + service_profile_name: test-sp |
| 99 | + state: absent |
| 100 | +''' |
| 101 | + |
| 102 | +RETURN = r''' |
| 103 | +assign_state: |
| 104 | + description: The logical server Assigned State (assigned, unassigned, or failed). |
| 105 | + returned: success |
| 106 | + type: string |
| 107 | + sample: assigned |
| 108 | +assoc_state: |
| 109 | + description: The logical server Association State (associated or unassociated). |
| 110 | + returned: success |
| 111 | + type: string |
| 112 | + sample: associated |
| 113 | +''' |
| 114 | + |
| 115 | +from ansible.module_utils.basic import AnsibleModule |
| 116 | +from ansible_collections.cisco.ucs.plugins.module_utils.ucs import UCSModule, ucs_argument_spec |
| 117 | + |
| 118 | + |
| 119 | +def main(): |
| 120 | + argument_spec = ucs_argument_spec |
| 121 | + argument_spec.update( |
| 122 | + org_dn=dict(type='str', default='org-root'), |
| 123 | + service_profile_name=dict(type='str', required=True), |
| 124 | + server_assignment=dict(type='str', choices=['server', 'pool']), |
| 125 | + server_dn=dict(type='str'), |
| 126 | + server_pool_name=dict(type='str'), |
| 127 | + restrict_migration=dict(type='str', default='no', choices=['yes', 'no']), |
| 128 | + state=dict(default='present', choices=['present', 'absent'], type='str'), |
| 129 | + ) |
| 130 | + module = AnsibleModule( |
| 131 | + argument_spec, |
| 132 | + supports_check_mode=True, |
| 133 | + required_if=[ |
| 134 | + ['state', 'present', ['server_assignment']], |
| 135 | + ['server_assignment', 'server', ['server_dn']], |
| 136 | + ['server_assignment', 'pool', ['server_pool_name']], |
| 137 | + ], |
| 138 | + mutually_exclusive=[ |
| 139 | + ['server_dn', 'server_pool_name'], |
| 140 | + ], |
| 141 | + ) |
| 142 | + # UCSModule verifies ucsmsdk is present and exits on failure. Imports are below ucs object creation. |
| 143 | + ucs = UCSModule(module) |
| 144 | + |
| 145 | + err = False |
| 146 | + |
| 147 | + from ucsmsdk.mometa.ls.LsRequirement import LsRequirement |
| 148 | + from ucsmsdk.mometa.ls.LsBinding import LsBinding |
| 149 | + from ucsmsdk.mometa.ls.LsServer import LsServer |
| 150 | + |
| 151 | + changed = False |
| 152 | + ucs.result['assign_state'] = 'unassigned' |
| 153 | + ucs.result['assoc_state'] = 'unassociated' |
| 154 | + try: |
| 155 | + ls_mo_exists = False |
| 156 | + pn_mo_exists = False |
| 157 | + pn_req_mo_exists = False |
| 158 | + props_match = False |
| 159 | + |
| 160 | + # logical server distinguished name is <org>/ls-<name> and physical node dn appends 'pn' or 'pn-req' |
| 161 | + ls_dn = module.params['org_dn'] + '/ls-' + module.params['service_profile_name'] |
| 162 | + ls_mo = ucs.login_handle.query_dn(ls_dn) |
| 163 | + if ls_mo: |
| 164 | + ls_mo_exists = True |
| 165 | + pn_dn = ls_dn + '/pn' |
| 166 | + pn_mo = ucs.login_handle.query_dn(pn_dn) |
| 167 | + if pn_mo: |
| 168 | + pn_mo_exists = True |
| 169 | + |
| 170 | + pn_req_dn = ls_dn + '/pn-req' |
| 171 | + pn_req_mo = ucs.login_handle.query_dn(pn_req_dn) |
| 172 | + if pn_req_mo: |
| 173 | + pn_req_mo_exists = True |
| 174 | + |
| 175 | + if module.params['state'] == 'absent': |
| 176 | + if ls_mo_exists and ls_mo.assign_state != 'unassigned': |
| 177 | + if pn_mo_exists: |
| 178 | + if not module.check_mode: |
| 179 | + ucs.login_handle.remove_mo(pn_mo) |
| 180 | + ucs.login_handle.commit() |
| 181 | + changed = True |
| 182 | + elif pn_req_mo_exists: |
| 183 | + if not module.check_mode: |
| 184 | + ucs.login_handle.remove_mo(pn_req_mo) |
| 185 | + ucs.login_handle.commit() |
| 186 | + changed = True |
| 187 | + elif ls_mo_exists: |
| 188 | + # check if logical server is assigned and associated |
| 189 | + ucs.result['assign_state'] = ls_mo.assign_state |
| 190 | + ucs.result['assoc_state'] = ls_mo.assoc_state |
| 191 | + if module.params['server_assignment'] == 'pool' and pn_req_mo_exists: |
| 192 | + # check the current pool |
| 193 | + kwargs = dict(name=module.params['server_pool_name']) |
| 194 | + kwargs['restrict_migration'] = module.params['restrict_migration'] |
| 195 | + if pn_req_mo.check_prop_match(**kwargs): |
| 196 | + props_match = True |
| 197 | + elif pn_mo_exists: |
| 198 | + kwargs = dict(pn_dn=module.params['server_dn']) |
| 199 | + kwargs['restrict_migration'] = module.params['restrict_migration'] |
| 200 | + if pn_mo.check_prop_match(**kwargs): |
| 201 | + props_match = True |
| 202 | + |
| 203 | + if not props_match: |
| 204 | + if not module.check_mode: |
| 205 | + # create if mo does not already exist in desired state |
| 206 | + mo = LsServer( |
| 207 | + parent_mo_or_dn=module.params['org_dn'], |
| 208 | + name=module.params['service_profile_name'], |
| 209 | + ) |
| 210 | + if module.params['server_assignment'] == 'pool': |
| 211 | + if pn_mo_exists: |
| 212 | + ucs.login_handle.remove_mo(pn_mo) |
| 213 | + |
| 214 | + mo_1 = LsRequirement( |
| 215 | + parent_mo_or_dn=mo, |
| 216 | + name=module.params['server_pool_name'], |
| 217 | + restrict_migration=module.params['restrict_migration'], |
| 218 | + ) |
| 219 | + else: |
| 220 | + mo_1 = LsBinding( |
| 221 | + parent_mo_or_dn=mo, |
| 222 | + pn_dn=module.params['server_dn'], |
| 223 | + restrict_migration=module.params['restrict_migration'], |
| 224 | + ) |
| 225 | + ucs.login_handle.add_mo(mo_1, True) |
| 226 | + ucs.login_handle.commit() |
| 227 | + |
| 228 | + pn_req_mo = ucs.login_handle.query_dn(pn_req_dn) |
| 229 | + if pn_req_mo: |
| 230 | + # profiles from templates will add a server pool, so remove and add the server again |
| 231 | + ucs.login_handle.remove_mo(pn_req_mo) |
| 232 | + |
| 233 | + ucs.login_handle.add_mo(mo_1, True) |
| 234 | + ucs.login_handle.commit() |
| 235 | + ls_mo = ucs.login_handle.query_dn(ls_dn) |
| 236 | + if ls_mo: |
| 237 | + ucs.result['assign_state'] = ls_mo.assign_state |
| 238 | + ucs.result['assoc_state'] = ls_mo.assoc_state |
| 239 | + changed = True |
| 240 | + |
| 241 | + except Exception as e: |
| 242 | + err = True |
| 243 | + ucs.result['msg'] = "setup error: %s " % str(e) |
| 244 | + |
| 245 | + ucs.result['changed'] = changed |
| 246 | + if err: |
| 247 | + module.fail_json(**ucs.result) |
| 248 | + module.exit_json(**ucs.result) |
| 249 | + |
| 250 | + |
| 251 | +if __name__ == '__main__': |
| 252 | + main() |
0 commit comments