1
# Copyright 2015 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
5
'power_control_msftocs',
7
'probe_and_enlist_msftocs',
14
from lxml.etree import fromstring
15
from provisioningserver.rpc.utils import (
19
from provisioningserver.utils.twisted import synchronous
22
class MicrosoftOCSState(object):
27
class MicrosoftOCSError(Exception):
28
"""Failure talking to a MicrosoftOCS chassis controller. """
31
class MicrosoftOCSAPI(object):
32
"""API to communicate with the Microsoft OCS Chassis Manager."""
34
def __init__(self, ip, port, username, password):
36
:param ip: The IP address of the MicrosoftOCS chassis,
39
:param port: The http port to connect to the MicrosoftOCS chassis,
42
:param username: The username for authentication to the MicrosoftOCS
43
chassis, e.g.: "admin"
44
:type username: string
45
:param password: The password for authentication to the MicrosoftOCS
46
chassis, e.g.: "password"
47
:type password: string
51
self.username = username
52
self.password = password
54
def build_url(self, command, params=[]):
55
url = 'http://%s:%d/' % (self.ip, self.port)
56
params = [param for param in params if bool(param)]
57
return urllib.parse.urljoin(url, command) + '?' + '&'.join(params)
59
def extract_from_response(self, response, element_tag):
60
"""Extract text from first element with element_tag in response."""
61
root = fromstring(response)
63
'.//ns:%s' % element_tag,
64
namespaces={'ns': root.nsmap[None]})
66
def get(self, command, params=None):
67
"""Dispatch a GET request to a Microsoft OCS chassis."""
68
url = self.build_url(command, params)
69
authinfo = urllib.request.HTTPPasswordMgrWithDefaultRealm()
70
authinfo.add_password(None, url, self.username, self.password)
71
proxy_handler = urllib.request.ProxyHandler({})
72
auth_handler = urllib.request.HTTPBasicAuthHandler(authinfo)
73
opener = urllib.request.build_opener(proxy_handler, auth_handler)
74
urllib.request.install_opener(opener)
75
response = urllib.request.urlopen(url)
76
return response.read()
78
def get_blade_power_state(self, bladeid):
79
"""Gets the ON/OFF State of Blade."""
80
params = ["bladeid=%s" % bladeid]
81
return self.extract_from_response(
82
self.get('GetBladeState', params), 'bladeState')
84
def _set_power(self, bladeid, element_tag):
85
"""Set AC Outlet Power for Blade."""
86
params = ["bladeid=%s" % bladeid]
87
return self.extract_from_response(
88
self.get(element_tag, params), 'completionCode')
90
def set_power_off_blade(self, bladeid):
91
"""Turns AC Outlet Power OFF for Blade."""
92
return self._set_power(bladeid, 'SetBladeOff')
94
def set_power_on_blade(self, bladeid):
95
"""Turns AC Outlet Power ON for Blade."""
96
return self._set_power(bladeid, 'SetBladeOn')
98
def set_next_boot_device(self, bladeid, pxe=False,
99
uefi=False, persistent=False):
100
"""Set Next Boot Device."""
101
boot_pxe = '2' if pxe else '3'
102
boot_uefi = 'true' if uefi else 'false'
103
boot_persistent = 'true' if persistent else 'false'
105
"bladeid=%s" % bladeid, "bootType=%s" % boot_pxe,
106
"uefi=%s" % boot_uefi, "persistent=%s" % boot_persistent
108
return self.extract_from_response(
109
self.get('SetNextBoot', params), 'nextBoot')
111
def get_blades(self):
112
"""Gets available Blades.
114
Returns dictionary of blade numbers and their corresponding
118
root = fromstring(self.get('GetChassisInfo'))
119
namespace = {'ns': root.nsmap[None]}
120
blade_collections = root.find(
121
'.//ns:bladeCollections', namespaces=namespace)
122
# Iterate over all BladeInfo Elements
123
for blade_info in blade_collections:
124
blade_mac_address = blade_info.find(
125
'.//ns:bladeMacAddress', namespaces=namespace)
127
# Iterate over all NicInfo Elements and add MAC Addresses
128
for nic_info in blade_mac_address:
131
'.//ns:macAddress', namespaces=namespace))
132
macs = [mac for mac in macs if bool(mac)]
134
# Retrive Blade id number
135
bladeid = blade_info.findtext(
136
'.//ns:bladeNumber', namespaces=namespace)
137
# Add MAC Addresses for Blade
138
blades[bladeid] = macs
143
def power_state_msftocs(ip, port, username, password, blade_id):
144
"""Return the power state for the given Blade."""
146
port = int(port) or 8000 # Default Port for MicrosoftOCS Chassis is 8000
147
api = MicrosoftOCSAPI(ip, port, username, password)
150
power_state = api.get_blade_power_state(blade_id)
151
except urllib.error.HTTPError as e:
152
raise MicrosoftOCSError(
153
"Failed to retrieve power state. HTTP error code: %s" % e.code)
154
except urllib.error.URLError as e:
155
raise MicrosoftOCSError(
156
"Failed to retrieve power state. Server could not be reached: %s"
159
if power_state == MicrosoftOCSState.OFF:
161
elif power_state == MicrosoftOCSState.ON:
163
raise MicrosoftOCSError('Unknown power state: %s' % power_state)
166
def power_control_msftocs(
167
ip, port, username, password, blade_id, power_change):
168
"""Control the power state for the given Blade."""
170
port = int(port) or 8000 # Default Port for MicrosoftOCS Chassis is 8000
171
api = MicrosoftOCSAPI(ip, port, username, password)
173
if power_change == 'on':
174
power_state = api.get_blade_power_state(blade_id)
175
if power_state == MicrosoftOCSState.ON:
176
api.set_power_off_blade(blade_id)
177
# Set default (persistent) boot to HDD
178
api.set_next_boot_device(blade_id, persistent=True)
179
# Set next boot to PXE
180
api.set_next_boot_device(blade_id, pxe=True)
181
api.set_power_on_blade(blade_id)
182
elif power_change == 'off':
183
api.set_power_off_blade(blade_id)
185
raise MicrosoftOCSError(
186
"Unexpected MAAS power mode: %s" % power_change)
190
def probe_and_enlist_msftocs(
191
user, ip, port, username, password, accept_all=False):
192
""" Extracts all of nodes from msftocs, sets all of them to boot via
193
HDD by, default, sets them to bootonce via PXE, and then enlists them
196
port = int(port) or 8000 # Default Port for MicrosoftOCS Chassis is 8000
197
api = MicrosoftOCSAPI(ip, port, username, password)
200
# if get_blades works, we have access to the system
201
blades = api.get_blades()
202
except urllib.error.HTTPError as e:
203
raise MicrosoftOCSError(
204
"Failed to probe nodes for Microsoft OCS with ip=%s "
205
"port=%d, username=%s, password=%s. HTTP error code: %s"
206
% (ip, port, username, password, e.code))
207
except urllib.error.URLError as e:
208
raise MicrosoftOCSError(
209
"Failed to probe nodes for Microsoft OCS with ip=%s "
210
"port=%d, username=%s, password=%s. "
211
"Server could not be reached: %s"
212
% (ip, port, username, password, e.reason))
214
for blade_id, macs in blades.items():
215
# Set default (persistent) boot to HDD
216
api.set_next_boot_device(blade_id, persistent=True)
217
# Set next boot to PXE
218
api.set_next_boot_device(blade_id, pxe=True)
222
'power_user': username,
223
'power_pass': password,
224
'blade_id': blade_id,
226
system_id = create_node(macs, 'amd64', 'msftocs', params).wait(30)
229
commission_node(system_id, user).wait(30)