diff --git a/lib/ansible/modules/cloud/hpe/hpe_icsp_os_deployment.py b/lib/ansible/modules/cloud/hpe/hpe_icsp_os_deployment.py
new file mode 100755
index 00000000000000..4c43e79f93f352
--- /dev/null
+++ b/lib/ansible/modules/cloud/hpe/hpe_icsp_os_deployment.py
@@ -0,0 +1,217 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (2016-2017) Hewlett Packard Enterprise Development LP
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+ANSIBLE_METADATA = {'status': ['stableinterface'],
+ 'supported_by': 'curated',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: hpe_icsp_os_deployment
+short_description: Deploy the operating system on a server using HPE ICsp.
+description:
+ - Deploy the operating system on a server based on the available ICsp OS build plan.
+requirements:
+ - "python >= 2.7.9"
+ - "hpICsp >= 1.0.2"
+version_added: "2.3"
+author:
+ - "Tiago Totti (@tiagomtotti)"
+ - "Chakravarthy Racharla (@ChakruHP)"
+options:
+ api_version:
+ description:
+ - ICsp API version.
+ required: false
+ default: 300
+ icsp_host:
+ description:
+ - ICsp hostname.
+ required: true
+ username:
+ description:
+ - ICsp username.
+ required: true
+ password:
+ description:
+ - ICsp password.
+ required: true
+ server_id:
+ description:
+ - Server ID.
+ required: true
+ os_build_plan:
+ description:
+ - OS Build plan.
+ required: true
+ custom_attributes:
+ description:
+ - Custom Attributes.
+ required: false
+ default: null
+ personality_data:
+ description:
+ - Personality Data.
+ required: false
+ default: null
+'''
+
+EXAMPLES = '''
+- name: Deploy OS
+ hpe_icsp_os_deployment:
+ icsp_host: "{{ icsp }}"
+ username: "{{ icsp_username }}"
+ password: "{{ icsp_password }}"
+ server_id: "{{ server_profile.serialNumber }}"
+ os_build_plan: "{{ os_build_plan }}"
+ custom_attributes: "{{ osbp_custom_attributes }}"
+ personality_data: "{{ network_config }}"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+icsp_server:
+ description: Has the facts about the server that was provisioned with ICsp.
+ returned: When the module runs successfully, but can be null.
+ type: complex
+'''
+
+from future import standard_library
+
+standard_library.install_aliases()
+
+import time
+import hpICsp
+from urllib.parse import quote
+from ansible.module_utils.basic import AnsibleModule
+
+
+def get_build_plan(con, bp_name):
+ search_uri = '/rest/index/resources?filter="name=\'' + quote(bp_name) + '\'"&category=osdbuildplan'
+ search_result = con.get(search_uri)
+
+ if search_result['count'] > 0 and search_result['members'][0]['name'] == bp_name:
+ return search_result['members'][0]
+ return None
+
+
+def get_server_by_serial(con, serial_number):
+ search_uri = '/rest/index/resources?category=osdserver&query=\'osdServerSerialNumber:\"' + serial_number + '\"\''
+ search_result = con.get(search_uri)
+ if search_result['count'] > 0:
+ same_serial_number = search_result['members'][0]['attributes']['osdServerSerialNumber'] == serial_number
+
+ if same_serial_number:
+ server_id = search_result['members'][0]['attributes']['osdServerId']
+ server = {'uri': '/rest/os-deployment-servers/' + server_id}
+ return server
+
+ return None
+
+
+def deploy_server(module):
+ # Credentials
+ icsp_host = module.params['icsp_host']
+ icsp_api_version = module.params['api_version']
+ username = module.params['username']
+ password = module.params['password']
+
+ # Build Plan Options
+ server_id = module.params['server_id']
+ os_build_plan = module.params['os_build_plan']
+ custom_attributes = module.params['custom_attributes']
+ personality_data = module.params['personality_data']
+ con = hpICsp.connection(icsp_host, icsp_api_version)
+
+ # Create objects for all necessary resources.
+ credential = {'userName': username, 'password': password}
+ con.login(credential)
+
+ bp = hpICsp.buildPlans(con)
+ jb = hpICsp.jobs(con)
+ sv = hpICsp.servers(con)
+
+ bp = get_build_plan(con, os_build_plan)
+
+ if bp is None:
+ return module.fail_json(msg='Cannot find OS Build plan: ' + os_build_plan)
+
+ timeout = 600
+ while True:
+ server = get_server_by_serial(con, server_id)
+ if server:
+ break
+ if timeout < 0:
+ module.fail_json(msg='Cannot find server in ICSP.')
+ return
+ timeout -= 30
+ time.sleep(30)
+
+ server = sv.get_server(server['uri'])
+ if server['state'] == 'OK':
+ return module.exit_json(changed=False, msg="Server already deployed.", ansible_facts={'icsp_server': server})
+
+ if custom_attributes:
+ ca_list = []
+
+ for ca in custom_attributes:
+ ca_list.append({
+ 'key': list(ca.keys())[0],
+ 'values': [{'scope': 'server', 'value': str(list(ca.values())[0])}]})
+
+ ca_list.extend(server['customAttributes'])
+ server['customAttributes'] = ca_list
+ sv.update_server(server)
+
+ server_data = {"serverUri": server['uri'], "personalityData": None}
+
+ build_plan_body = {"osbpUris": [bp['uri']], "serverData": [server_data], "stepNo": 1}
+
+ hpICsp.common.monitor_execution(jb.add_job(build_plan_body), jb)
+
+ # If the playbook included network personalization, update the server to include it
+ if personality_data:
+ server_data['personalityData'] = personality_data
+ network_config = {"serverData": [server_data]}
+ # Monitor the execution of a nework personalization job.
+ hpICsp.common.monitor_execution(jb.add_job(network_config), jb)
+
+ server = sv.get_server(server['uri'])
+ return module.exit_json(changed=True, msg='OS Deployed Successfully.', ansible_facts={'icsp_server': server})
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ api_version=dict(type='int', default=300),
+ icsp_host=dict(required=True, type='str'),
+ username=dict(required=True, type='str'),
+ password=dict(required=True, type='str', no_log=True),
+ server_id=dict(required=True, type='str'),
+ os_build_plan=dict(required=True, type='str'),
+ custom_attributes=dict(required=False, type='list', default=None),
+ personality_data=dict(required=False, type='dict', default=None)
+ ))
+
+ deploy_server(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/cloud/hpe/hpe_icsp_server.py b/lib/ansible/modules/cloud/hpe/hpe_icsp_server.py
new file mode 100755
index 00000000000000..0555647c3afa95
--- /dev/null
+++ b/lib/ansible/modules/cloud/hpe/hpe_icsp_server.py
@@ -0,0 +1,293 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (2016-2017) Hewlett Packard Enterprise Development LP
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+ANSIBLE_METADATA = {'status': ['stableinterface'],
+ 'supported_by': 'curated',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: hpe_icsp_server
+short_description: Adds, removes and configures servers in ICsp.
+description:
+ - This module allows you to add, remove and configure servers in the Insight Control Server Provisioning (ICsp).
+ In ICsp, a server, often referred to as a Target Server, is a physical ProLiant server or a virtual machine that
+ can have actions taken upon it.
+requirements:
+ - "python >= 2.7.9"
+ - "hpICsp >= 1.0.2"
+version_added: "2.3"
+author:
+ - "Tiago Totti (@tiagomtotti)"
+options:
+ state:
+ description:
+ - Indicates the desired state for the ICsp server.
+ 'present' will register the resource on ICsp.
+ 'absent' will remove the resource from ICsp, if it exists.
+ 'network_configured' will set the network configuration.
+ choices: ['present', 'absent', 'network_configured']
+ api_version:
+ description:
+ - ICsp API version.
+ required: false
+ default: 300
+ icsp_host:
+ description:
+ - ICsp hostname.
+ required: true
+ username:
+ description:
+ - ICsp username.
+ required: true
+ password:
+ description:
+ - ICsp password.
+ required: true
+ server_ipAddress:
+ description:
+ - The IP address of the iLO of the server.
+ required: true
+ server_username:
+ description:
+ - The username required to log into the server's iLO.
+ required: true
+ server_password:
+ description:
+ - The password required to log into the server's iLO
+ required: true
+ server_port:
+ description:
+ - The iLO port to use when logging in.
+ default:
+ - 443
+ required: false
+ server_personality_data:
+ description:
+ - Additional data to send to ICsp.
+ required: false
+'''
+
+EXAMPLES = '''
+ - name: Ensure the server is registered in ICsp
+ hpe_icsp_server:
+ icsp_host: "{{icsp_host}}"
+ username: "{{icsp_username}}"
+ password: "{{icsp_password}}"
+ server_ipAddress: "{{server_iLO_ip}}"
+ server_username: "Admin"
+ server_password: "admin"
+ state: present
+ delegate_to: localhost
+
+ - name: Set the network configuration
+ hpe_icsp_server:
+ icsp_host: "{{ icsp }}"
+ username: "{{ icsp_username }}"
+ password: "{{ icsp_password }}"
+ server_ipAddress: "{{ server_ipAddress }}"
+ server_username: "{{ server_username }}"
+ server_password: "{{ server_password }}"
+ server_personality_data: "{{ network_config }}"
+ state: network_configured
+ delegate_to: localhost
+
+ - name: Ensure the server is removed from ICsp
+ hpe_icsp_server:
+ icsp_host: "{{icsp_host}}"
+ username: "{{icsp_username}}"
+ password: "{{icsp_password}}"
+ server_ipAddress: "{{server_iLO_ip}}"
+ server_username: "Admin"
+ server_password: "admin"
+ state: absent
+ delegate_to: localhost
+'''
+
+RETURN = '''
+target_server:
+ description: Has the facts about the server that was added to ICsp.
+ returned: On states 'present' and 'network_configured' . Can be null.
+ type: complex
+'''
+
+import json
+import hpICsp
+from hpICsp.exceptions import HPICspException
+from ansible.module_utils.basic import AnsibleModule
+
+
+class ICspServerModule(object):
+ SERVER_CREATED = "Server created: '{}'"
+ SERVER_ALREADY_PRESENT = "Server is already present."
+ SERVER_ALREADY_ABSENT = "Target server is already absent in ICsp."
+ SERVER_REMOVED = "Server '{}' removed successfully from ICsp."
+ CUSTOM_ATTR_NETWORK_UPDATED = 'Network Custom Attribute Updated.'
+ SERVER_NOT_FOUND = "Target server is not present in ICsp."
+ SERVER_PERSONALITY_DATA_REQUIRED = 'server_personality_data must be informed.'
+
+ argument_spec = dict(
+ # Connection
+ api_version=dict(type='int', default=300),
+ icsp_host=dict(required=True, type='str'),
+ username=dict(required=True, type='str'),
+ password=dict(required=True, type='str', no_log=True),
+ # options
+ state=dict(
+ required=True,
+ choices=['present', 'absent', 'network_configured']
+ ),
+ # server data
+ server_ipAddress=dict(required=True, type='str'),
+ server_username=dict(required=True, type='str'),
+ server_password=dict(required=True, type='str', no_log=True),
+ server_port=dict(type='int', default=443),
+ server_personality_data=dict(required=False, type='dict')
+ )
+
+ def __init__(self):
+ self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=False)
+ self.connection = self.__authenticate()
+
+ def run(self):
+
+ state = self.module.params['state']
+ ilo_address = self.module.params['server_ipAddress']
+ target_server = self.__get_server_by_ilo_address(ilo_address)
+
+ if state == 'present':
+ self.__present(target_server)
+
+ elif state == 'absent':
+ self.__absent(target_server)
+
+ elif state == 'network_configured':
+ self.__configure_network(target_server)
+
+ def __authenticate(self):
+ # Credentials
+ icsp_host = self.module.params['icsp_host']
+ icsp_api_version = self.module.params['api_version']
+ username = self.module.params['username']
+ password = self.module.params['password']
+
+ con = hpICsp.connection(icsp_host, icsp_api_version)
+
+ credential = {'userName': username, 'password': password}
+ con.login(credential)
+ return con
+
+ def __present(self, target_server):
+ # check if server exists
+ if target_server:
+ return self.module.exit_json(changed=False,
+ msg=self.SERVER_ALREADY_PRESENT,
+ ansible_facts=dict(target_server=target_server))
+
+ return self._add_server()
+
+ def __absent(self, target_server):
+ # check if server exists
+ if not target_server:
+ return self.module.exit_json(changed=False, msg=self.SERVER_ALREADY_ABSENT)
+
+ server_uri = target_server['uri']
+ servers_service = hpICsp.servers(self.connection)
+
+ try:
+ servers_service.delete_server(server_uri)
+ return self.module.exit_json(changed=True,
+ msg=self.SERVER_REMOVED.format(server_uri))
+
+ except HPICspException as icsp_exe:
+ self.module.fail_json(msg=json.dumps(icsp_exe.__dict__))
+
+ except Exception as exception:
+ self.module.fail_json(msg='; '.join(str(e) for e in exception.args))
+
+ def __configure_network(self, target_server):
+ personality_data = self.module.params.get('server_personality_data')
+
+ if not personality_data:
+ return self.module.fail_json(msg=self.SERVER_PERSONALITY_DATA_REQUIRED)
+
+ # check if server exists
+ if not target_server:
+ return self.module.exit_json(changed=False, msg=self.SERVER_NOT_FOUND)
+
+ server_data = {"serverUri": target_server['uri'], "personalityData": personality_data, "skipReboot": True}
+ network_config = {"serverData": [server_data], "failMode": None, "osbpUris": []}
+
+ # Save nework personalization attribute, without running the job
+ self.__add_write_only_job(network_config)
+
+ servers_service = hpICsp.servers(self.connection)
+ server = servers_service.get_server(target_server['uri'])
+ return self.module.exit_json(changed=True,
+ msg=self.CUSTOM_ATTR_NETWORK_UPDATED,
+ ansible_facts={'target_server': server})
+
+ def __add_write_only_job(self, body):
+ body = self.connection.post("/rest/os-deployment-jobs/?writeOnly=true", body)
+ return body
+
+ def __get_server_by_ilo_address(self, ilo):
+ servers = self.connection.get("/rest/os-deployment-servers/?count=-1")
+ srv = self.__filter_by_ilo(servers['members'], ilo)
+ return srv
+
+ def __filter_by_ilo(self, seq, value):
+ for srv in seq:
+ if srv['ilo']['ipAddress'] == value:
+ return srv
+ return None
+
+ def _add_server(self):
+ ilo_address = self.module.params['server_ipAddress']
+
+ # Creates a JSON body for adding an iLo.
+ ilo_body = {'ipAddress': ilo_address,
+ 'username': self.module.params['server_username'],
+ 'password': self.module.params['server_password'],
+ 'port': self.module.params['server_port']}
+
+ job_monitor = hpICsp.jobs(self.connection)
+ servers_service = hpICsp.servers(self.connection)
+
+ # Monitor_execution is a utility method to watch job progress on the command line.
+ add_server_job = servers_service.add_server(ilo_body)
+ hpICsp.common.monitor_execution(add_server_job, job_monitor)
+
+ # Python bindings throw an Exception when the status != ok
+ # So if we got this far, the job execution finished as expected
+
+ # gets the target server added to ICsp to return on ansible facts
+ target_server = self.__get_server_by_ilo_address(ilo_address)
+ return self.module.exit_json(changed=True,
+ msg=self.SERVER_CREATED.format(target_server['uri']),
+ ansible_facts=dict(target_server=target_server))
+
+
+def main():
+ ICspServerModule().run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/units/modules/cloud/hpe/test_hpe_icsp_os_deployment.py b/test/units/modules/cloud/hpe/test_hpe_icsp_os_deployment.py
new file mode 100755
index 00000000000000..16ee7228206fcb
--- /dev/null
+++ b/test/units/modules/cloud/hpe/test_hpe_icsp_os_deployment.py
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (2016-2017) Hewlett Packard Enterprise Development LP
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+import unittest
+import mock
+import ansible.modules.cloud.hpe.hpe_icsp_os_deployment
+from copy import deepcopy
+
+MODULE_NAME = 'ansible.modules.cloud.hpe.hpe_icsp_os_deployment'
+
+TASK_OS_DEPLOYMENT = {
+ "icsp_host": "16.124.133.251",
+ "api_version": 300,
+ "username": "Administrator",
+ "password": "admin",
+ "server_id": "VCGYZ33007",
+ "os_build_plan": "RHEL 7.2 x64",
+ "personality_data": None,
+ "custom_attributes": None
+}
+
+DEFAULT_SERVER = {"name": "SP-01",
+ "uri": "/uri/239",
+ "ilo": {"ipAddress": "16.124.135.239"},
+ "state": "",
+ 'attributes': {'osdServerId': '123456',
+ 'osdServerSerialNumber': 'VCGYZ33007'},
+ "customAttributes": []}
+
+DEFAULT_SERVER_UPDATED = {"name": "SP-01",
+ "uri": "/uri/239",
+ "ilo": {"ipAddress": "16.124.135.239"},
+ "state": "OK",
+ "customAttributes":
+ [{'values': [{'scope': 'server', 'value': "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"}],
+ 'key': 'SSH_CERT'}]}
+
+DEFAULT_BUILD_PLAN = {"name": "RHEL 7.2 x64", "uri": "/rest/os-deployment-build-plans/222"}
+
+
+class IcspOsDeploymentSpec(unittest.TestCase):
+ def setUp(self):
+ self.patcher_ansible_module = mock.patch(MODULE_NAME + '.AnsibleModule')
+ self.mock_ansible_module = self.patcher_ansible_module.start()
+
+ self.mock_ansible_instance = mock.Mock()
+ self.mock_ansible_module.return_value = self.mock_ansible_instance
+
+ self.patcher_icsp_service = mock.patch(MODULE_NAME + '.hpICsp')
+ self.mock_icsp = self.patcher_icsp_service.start()
+
+ self.patcher_time_sleep = mock.patch('time.sleep', return_value=None)
+ self.mock_time_sleep = self.patcher_time_sleep.start()
+
+ self.mock_connection = mock.Mock()
+ self.mock_connection.login.return_value = {}
+ self.mock_icsp.connection.return_value = self.mock_connection
+
+ self.mock_icsp_common = mock.Mock()
+ self.mock_icsp.common.return_value = self.mock_icsp_common
+
+ self.mock_icsp_jobs = mock.Mock()
+ self.mock_icsp.jobs.return_value = self.mock_icsp_jobs
+
+ self.mock_server_service = mock.Mock()
+ self.mock_icsp.servers.return_value = self.mock_server_service
+
+ self.mock_build_plans_service = mock.Mock()
+ self.mock_icsp.buildPlans.return_value = self.mock_build_plans_service
+
+ def tearDown(self):
+ self.patcher_ansible_module.stop()
+ self.patcher_icsp_service.stop()
+ self.patcher_time_sleep.stop()
+
+ def get_as_rest_collection(self, server):
+ return {
+ 'members': server,
+ 'count': len(server)
+ }
+
+ def test_should_not_add_server_when_already_present(self):
+ server_already_deployed = dict(DEFAULT_SERVER, state="OK")
+
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.return_value = server_already_deployed
+
+ self.mock_ansible_instance.params = TASK_OS_DEPLOYMENT
+
+ hpe_icsp_os_deployment.main()
+
+ self.mock_time_sleep.assert_not_called()
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=False, msg="Server already deployed.", ansible_facts={'icsp_server': server_already_deployed}
+ )
+
+ def test_should_fail_after_try_get_server_by_serial_21_times(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN])]
+
+ self.mock_server_service.get_server.return_value = DEFAULT_SERVER
+
+ self.mock_ansible_instance.params = TASK_OS_DEPLOYMENT
+
+ with mock.patch(MODULE_NAME + '.get_server_by_serial') as mock_get_srv_ser:
+ mock_get_srv_ser.return_value = None
+ hpe_icsp_os_deployment.main()
+
+ times_sleep_called = self.mock_time_sleep.call_count
+ self.assertEqual(21, times_sleep_called)
+
+ self.mock_ansible_instance.fail_json.assert_called_once_with(msg='Cannot find server in ICSP.')
+
+ def test_should_deploy_server(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.side_effect = [DEFAULT_SERVER, DEFAULT_SERVER_UPDATED]
+
+ self.mock_ansible_instance.params = TASK_OS_DEPLOYMENT
+
+ hpe_icsp_os_deployment.main()
+
+ times_sleep_called = self.mock_time_sleep.call_count
+ self.assertEqual(0, times_sleep_called)
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(changed=True, msg='OS Deployed Successfully.',
+ ansible_facts={
+ 'icsp_server': DEFAULT_SERVER_UPDATED})
+
+ def test_should_try_deploy_server_3_times(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN]),
+ self.get_as_rest_collection([]),
+ self.get_as_rest_collection([]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.side_effect = [DEFAULT_SERVER, DEFAULT_SERVER_UPDATED]
+
+ self.mock_ansible_instance.params = TASK_OS_DEPLOYMENT
+
+ hpe_icsp_os_deployment.main()
+
+ times_sleep_called = self.mock_time_sleep.call_count
+ self.assertEqual(2, times_sleep_called)
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(changed=True, msg='OS Deployed Successfully.',
+ ansible_facts={
+ 'icsp_server': DEFAULT_SERVER_UPDATED})
+
+ def test_should_fail_when_os_build_plan_not_found(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([]),
+ self.get_as_rest_collection([]),
+ self.get_as_rest_collection([]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.return_value = DEFAULT_SERVER
+
+ self.mock_ansible_instance.params = TASK_OS_DEPLOYMENT
+
+ hpe_icsp_os_deployment.main()
+
+ self.mock_ansible_instance.fail_json.assert_called_once_with(msg='Cannot find OS Build plan: RHEL 7.2 x64')
+
+ def test_should_update_server_when_task_include_network_personalization(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.side_effect = [DEFAULT_SERVER, DEFAULT_SERVER_UPDATED]
+ self.mock_icsp.common.monitor_execution.return_value = {}
+ self.mock_icsp_jobs.add_job.return_value = {"job mock return"}
+
+ task_with_network_personalization = deepcopy(TASK_OS_DEPLOYMENT)
+ network_config = {"network_config": {"hostname": "test-web.io.fc.hpe.com", "domain": "demo.com"}}
+ task_with_network_personalization['personality_data'] = network_config
+ self.mock_ansible_instance.params = task_with_network_personalization
+
+ hpe_icsp_os_deployment.main()
+
+ server_data = {"serverUri": DEFAULT_SERVER['uri'], "personalityData": None}
+ network_config = {"serverData": [server_data]}
+ build_plan_body = {"osbpUris": [DEFAULT_BUILD_PLAN['uri']], "serverData": [server_data], "stepNo": 1}
+
+ monitor_build_plan = mock.call(self.mock_icsp_jobs.add_job(build_plan_body), self.mock_icsp_jobs)
+ monitor_update_server = mock.call(self.mock_icsp_jobs.add_job(network_config), self.mock_icsp_jobs)
+ calls = [monitor_build_plan, monitor_update_server]
+
+ self.mock_icsp.common.monitor_execution.assert_has_calls(calls)
+
+ def test_should_update_server_when_task_include_custom_attributes(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN]),
+ self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ self.mock_server_service.get_server.side_effect = [DEFAULT_SERVER, DEFAULT_SERVER_UPDATED]
+ self.mock_server_service.update_server.return_value = DEFAULT_SERVER_UPDATED
+
+ task_with_custom_attr = deepcopy(TASK_OS_DEPLOYMENT)
+ custom_attr = [{"SSH_CERT": "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"}]
+ task_with_custom_attr['custom_attributes'] = custom_attr
+ self.mock_ansible_instance.params = task_with_custom_attr
+
+ hpe_icsp_os_deployment.main()
+
+ personality_data = deepcopy(DEFAULT_SERVER)
+ personality_data['customAttributes'] = [
+ {'values': [{'scope': 'server', 'value': "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"}],
+ 'key': 'SSH_CERT'}]
+
+ self.mock_server_service.update_server.assert_called_once_with(personality_data)
+
+ def test_get_server_by_serial_with_matching_result(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ server = hpe_icsp_os_deployment.get_server_by_serial(self.mock_connection, 'VCGYZ33007')
+
+ expected = {'uri': '/rest/os-deployment-servers/123456'}
+ self.mock_connection.get.assert_called_once_with(
+ '/rest/index/resources?category=osdserver&query=\'osdServerSerialNumber:"VCGYZ33007"\'')
+
+ self.assertEqual(server, expected)
+
+ def test_get_server_by_serial_with_non_matching_result(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_SERVER])]
+
+ server = hpe_icsp_os_deployment.get_server_by_serial(self.mock_connection, '000')
+
+ self.mock_connection.get.assert_called_once_with(
+ '/rest/index/resources?category=osdserver&query=\'osdServerSerialNumber:"000"\'')
+
+ self.assertIsNone(server)
+
+ def test_get_build_plan_with_matching_result(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN])]
+
+ server = hpe_icsp_os_deployment.get_build_plan(self.mock_connection, 'RHEL 7.2 x64')
+
+ expected = {'name': 'RHEL 7.2 x64', 'uri': '/rest/os-deployment-build-plans/222'}
+ self.mock_connection.get.assert_called_once_with(
+ '/rest/index/resources?filter="name=\'RHEL%207.2%20x64\'"&category=osdbuildplan')
+
+ self.assertEqual(server, expected)
+
+ def test_get_build_plan_with_non_matching_result(self):
+ self.mock_connection.get.side_effect = [self.get_as_rest_collection([DEFAULT_BUILD_PLAN])]
+
+ server = hpe_icsp_os_deployment.get_build_plan(self.mock_connection, 'BuildPlan')
+
+ self.mock_connection.get.assert_called_once_with(
+ '/rest/index/resources?filter="name=\'BuildPlan\'"&category=osdbuildplan')
+
+ self.assertIsNone(server)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/units/modules/cloud/hpe/test_hpe_icsp_server.py b/test/units/modules/cloud/hpe/test_hpe_icsp_server.py
new file mode 100755
index 00000000000000..341bb2d751f410
--- /dev/null
+++ b/test/units/modules/cloud/hpe/test_hpe_icsp_server.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (2016-2017) Hewlett Packard Enterprise Development LP
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+import unittest
+import mock
+import yaml
+import json
+
+from ansible.modules.cloud.hpe.hpe_icsp_server import (ICspServerModule,
+ main as hpe_icsp_server_main)
+
+from hpICsp.exceptions import HPICspInvalidResource
+
+MODULE_NAME = 'ansible.modules.cloud.hpe.hpe_icsp_server'
+
+SERVER_IP = "16.124.135.239"
+
+YAML_SERVER_PRESENT = """
+ state: present
+ api_version: 300
+ icsp_host: "16.124.133.245"
+ username: "Administrator"
+ password: "admin"
+ server_ipAddress: "16.124.135.239"
+ server_username: "Admin"
+ server_password: "serveradmin"
+ server_port: 443
+"""
+
+YAML_SERVER_ABSENT = """
+ state: absent
+ api_version: 300
+ icsp_host: "16.124.133.251"
+ username: "Administrator"
+ password: "admin"
+ server_ipAddress: "16.124.135.239"
+"""
+
+YAML_NETWORK_CONFIGURED = """
+ state: network_configured
+ api_version: 300
+ icsp_host: "16.124.133.245"
+ username: "Administrator"
+ password: "admin"
+ server_ipAddress: "16.124.135.239"
+ server_username: "Admin"
+ server_password: "serveradmin"
+ server_port: 443
+ server_personality_data:
+ network_config:
+ hostname: "test-web.io.fc.hpe.com"
+ domain: "demo.com"
+ interfaces:
+ - macAddress: "01:23:45:67:89:ab"
+ enabled: true
+ dhcpv4: false
+ ipv6Autoconfig:
+ dnsServers:
+ - "16.124.133.2"
+ staticNetworks:
+ - "16.124.133.39/255.255.255.0"
+ vlanid: -1
+ ipv4gateway: "16.124.133.1"
+ ipv6gateway:
+ virtualInterfaces:
+"""
+
+DEFAULT_SERVER = {"name": "SP-01", "uri": "/uri/239", "ilo": {"ipAddress": SERVER_IP}}
+SERVER_ADDED = {"name": "SP-03", "uri": "/uri/188", "ilo": {"ipAddress": "16.124.135.188"}}
+
+SERVERS = {
+ "members": [
+ DEFAULT_SERVER,
+ {"name": "SP-02", "uri": "/uri/233", "ilo": {"ipAddress": "16.124.135.233"}}
+ ]
+}
+
+CONNECTION = {}
+ICSP_JOBS = {}
+
+JOB_RESOURCE = {"uri": "/rest/os-deployment-jobs/123456"}
+
+
+class IcspServerSpec(unittest.TestCase):
+ def setUp(self):
+ self.patcher_ansible_module = mock.patch(MODULE_NAME + '.AnsibleModule')
+ self.mock_ansible_module = self.patcher_ansible_module.start()
+
+ self.mock_ansible_instance = mock.Mock()
+ self.mock_ansible_module.return_value = self.mock_ansible_instance
+
+ self.patcher_icsp_service = mock.patch(MODULE_NAME + '.hpICsp')
+ self.mock_icsp = self.patcher_icsp_service.start()
+
+ self.mock_connection = mock.Mock()
+ self.mock_connection.login.return_value = CONNECTION
+ self.mock_icsp.connection.return_value = self.mock_connection
+
+ self.mock_server_service = mock.Mock()
+ self.mock_icsp.servers.return_value = self.mock_server_service
+
+ def tearDown(self):
+ self.patcher_ansible_module.stop()
+ self.patcher_icsp_service.stop()
+
+ def test_should_not_add_server_when_already_present(self):
+ self.mock_connection.get.return_value = SERVERS
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_PRESENT)
+
+ ICspServerModule().run()
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=False,
+ msg=ICspServerModule.SERVER_ALREADY_PRESENT,
+ ansible_facts=dict(target_server=DEFAULT_SERVER)
+ )
+
+ def test_should_add_server(self):
+ self.mock_connection.get.side_effect = [{'members': []}, SERVERS]
+ self.mock_server_service.add_server.return_value = JOB_RESOURCE
+ self.mock_icsp.jobs.return_value = ICSP_JOBS
+
+ self.mock_icsp.common = mock.Mock()
+ self.mock_icsp.common.monitor_execution.return_value = {}
+
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_PRESENT)
+
+ hpe_icsp_server_main()
+
+ ilo_body = {'ipAddress': "16.124.135.239",
+ 'username': "Admin",
+ 'password': "serveradmin",
+ 'port': 443}
+ self.mock_server_service.add_server.assert_called_once_with(ilo_body)
+ self.mock_icsp.common.monitor_execution.assert_called_once_with(JOB_RESOURCE, ICSP_JOBS)
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=True,
+ msg="Server created: '/uri/239'",
+ ansible_facts=dict(target_server=DEFAULT_SERVER)
+ )
+
+ def test_expect_exception_not_caught_when_create_server_raise_exception(self):
+ self.mock_connection.get.side_effect = [{'members': []}, SERVERS]
+ self.mock_server_service.add_server.side_effect = Exception("message")
+
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_PRESENT)
+
+ try:
+ ICspServerModule().run()
+ except Exception as e:
+ self.assertEqual("message", e.args[0])
+ else:
+ self.fail("Expected Exception was not raised")
+
+ def test_should_not_try_delete_server_when_it_is_already_absent(self):
+ self.mock_connection.get.return_value = {'members': []}
+ self.mock_server_service.delete_server.return_value = {}
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_ABSENT)
+
+ ICspServerModule().run()
+
+ self.mock_server_service.delete_server.assert_not_called()
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=False,
+ msg=ICspServerModule.SERVER_ALREADY_ABSENT
+ )
+
+ def test_should_delete_server(self):
+ self.mock_connection.get.return_value = SERVERS
+
+ self.mock_server_service.delete_server.return_value = {}
+
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_ABSENT)
+
+ ICspServerModule().run()
+
+ self.mock_server_service.delete_server.assert_called_once_with("/uri/239")
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=True,
+ msg="Server '/uri/239' removed successfully from ICsp."
+ )
+
+ def test_should_fail_with_all_exe_attr_when_HPICspException_raised_on_delete(self):
+ self.mock_connection.get.return_value = SERVERS
+ exeption_value = {"message": "Fake Message", "details": "Details", "errorCode": "INVALID_RESOURCE"}
+ self.mock_server_service.delete_server.side_effect = HPICspInvalidResource(exeption_value)
+
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_ABSENT)
+
+ ICspServerModule().run()
+
+ # Load called args and convert to dict to prevent str comparison with different reordering (Python 3.5)
+ fail_json_args_msg = self.mock_ansible_instance.fail_json.call_args[1]['msg']
+ error_raised = json.loads(fail_json_args_msg)
+ self.assertEqual(error_raised, exeption_value)
+
+ def test_should_fail_with_args_joined_when_common_exception_raised_on_delete(self):
+ self.mock_connection.get.return_value = SERVERS
+ self.mock_server_service.delete_server.side_effect = Exception("Fake Message", "INVALID_RESOURCE")
+
+ self.mock_ansible_instance.params = yaml.load(YAML_SERVER_ABSENT)
+
+ ICspServerModule().run()
+
+ self.mock_ansible_instance.fail_json.assert_called_once_with(msg='Fake Message; INVALID_RESOURCE')
+
+ def test_should_configure_network(self):
+ self.mock_connection.get.side_effect = [SERVERS, SERVERS]
+ self.mock_connection.post.return_value = JOB_RESOURCE
+ self.mock_server_service.get_server.return_value = DEFAULT_SERVER
+
+ self.mock_ansible_instance.params = yaml.load(YAML_NETWORK_CONFIGURED)
+
+ ICspServerModule().run()
+
+ network_config_state = yaml.load(YAML_NETWORK_CONFIGURED)
+
+ network_config = {
+ "serverData": [
+ {"serverUri": DEFAULT_SERVER['uri'], "personalityData": network_config_state['server_personality_data'],
+ "skipReboot": True}],
+ "failMode": None,
+ "osbpUris": []
+ }
+
+ uri = '/rest/os-deployment-jobs/?writeOnly=true'
+
+ self.mock_connection.post.assert_called_once_with(uri, network_config)
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(
+ changed=True,
+ msg=ICspServerModule.CUSTOM_ATTR_NETWORK_UPDATED,
+ ansible_facts=dict(target_server=DEFAULT_SERVER)
+ )
+
+ def test_should_fail_when_try_configure_network_without_inform_personality_data(self):
+ self.mock_connection.get.return_value = SERVERS
+ self.mock_server_service.get_server.return_value = DEFAULT_SERVER
+
+ params_config_network = yaml.load(YAML_NETWORK_CONFIGURED)
+ params_config_network['server_personality_data'] = {}
+
+ self.mock_ansible_instance.params = params_config_network
+
+ ICspServerModule().run()
+
+ self.mock_ansible_instance.fail_json.assert_called_once_with(
+ msg=ICspServerModule.SERVER_PERSONALITY_DATA_REQUIRED)
+
+ def test_should_fail_when_try_configure_network_for_not_found_server(self):
+ self.mock_connection.get.return_value = {'members': []}
+
+ self.mock_ansible_instance.params = yaml.load(YAML_NETWORK_CONFIGURED)
+
+ ICspServerModule().run()
+
+ self.mock_ansible_instance.exit_json.assert_called_once_with(changed=False,
+ msg=ICspServerModule.SERVER_NOT_FOUND)
+
+ def test_expect_exception_not_caught_when_configure_network_raise_exception(self):
+ self.mock_connection.get.return_value = SERVERS
+ self.mock_connection.post.side_effect = Exception("message")
+
+ self.mock_ansible_instance.params = yaml.load(YAML_NETWORK_CONFIGURED)
+
+ try:
+ hpe_icsp_server_main()
+ except Exception as e:
+ self.assertEqual("message", e.args[0])
+ else:
+ self.fail("Expected Exception was not raised")
+
+
+if __name__ == '__main__':
+ unittest.main()