12
12
__metaclass__ = type
14
'power_control_seamicro15k_v09',
15
'power_control_seamicro15k_v2',
16
'probe_seamicro15k_and_enlist',
17
from StringIO import StringIO
21
from provisioningserver.custom_hardware.utils import create_node
26
import provisioningserver.custom_hardware.utils as utils
27
from seamicroclient import (
28
client as seamicro_client,
29
exceptions as seamicro_exceptions,
24
33
logger = logging.getLogger(__name__)
27
class SeaMicroAPIError(Exception):
42
class SeaMicroError(Exception):
28
43
"""Failure talking to a SeaMicro chassis controller. """
31
class SeaMicroAPI(object):
32
def __init__(self, url, username=None, password=None):
47
class SeaMicroAPIV09Error(SeaMicroError):
48
"""Failure talking to a SeaMicro API v0.9. """
50
def __init__(self, msg, response_code=None):
51
super(SeaMicroAPIV09Error, self).__init__(msg)
52
self.response_code = response_code
55
class SeaMicroAPIV09(object):
56
allowed_codes = [httplib.OK, httplib.ACCEPTED, httplib.NOT_MODIFIED]
58
def __init__(self, url):
34
60
:param url: The URL of the seamicro chassis, e.g.: http://seamciro/v0.9
38
self.token = self._get("login", [username, password])
40
def _get(self, location, params=None):
41
"""Dispatch a GET request to a SeaMicro chassis.
43
The seamicro box has order-dependent HTTP parameters, so we build
44
our own get URL, and use a list vs. a dict for data, as the order is
66
def build_url(self, location, params=None):
67
"""Builds an order-dependent url, as the SeaMicro chassis
68
requires order-dependent parameters.
49
72
params = filter(None, params)
51
allowed_codes = [httplib.OK, httplib.ACCEPTED, httplib.NOT_MODIFIED]
53
url = urlparse.urljoin(self.url, location) + '?' + '&'.join(params)
54
response = urllib2.urlopen(url)
55
if response.getcode() not in allowed_codes:
56
raise SeaMicroAPIError("got response code %d" % response.getcode())
73
return urlparse.urljoin(self.url, location) + '?' + '&'.join(params)
75
def parse_response(self, url, response):
76
"""Parses the HTTP response, checking for errors
77
from the SeaMicro chassis.
79
if response.getcode() not in self.allowed_codes:
80
raise SeaMicroAPIV09Error(
81
"got response code %s" % response.getcode(),
82
response_code=response.getcode())
57
83
text = response.read()
58
json_data = json.load(StringIO(text))
85
# Decode the response, it should be json. If not
86
# handle that case and set json_data to None, so
87
# a SeaMicroAPIV09Error can be raised.
89
json_data = json.loads(text)
61
raise SeaMicroAPIError(
94
raise SeaMicroAPIV09Error(
62
95
'No JSON data found from %s: got %s' % (url, text))
63
96
json_rpc_code = int(json_data['error']['code'])
64
if json_rpc_code not in allowed_codes:
65
raise SeaMicroAPIError(
97
if json_rpc_code not in self.allowed_codes:
98
raise SeaMicroAPIV09Error(
66
99
'Got JSON RPC error code %d: %s for %s' % (
68
101
httplib.responses.get(json_rpc_code, 'Unknown!'),
71
return json_data['result']
103
response_code=json_rpc_code)
106
def get(self, location, params=None):
107
"""Dispatch a GET request to a SeaMicro chassis.
109
The seamicro box has order-dependent HTTP parameters, so we build
110
our own get URL, and use a list vs. a dict for data, as the order is
113
url = self.build_url(location, params)
114
response = urllib2.urlopen(url)
115
json_data = self.parse_response(url, response)
117
return json_data['result']
119
def put(self, location, params=None):
120
"""Dispatch a PUT request to a SeaMicro chassis.
122
The seamicro box has order-dependent HTTP parameters, so we build
123
our own get URL, and use a list vs. a dict for data, as the order is
126
opener = urllib2.build_opener(urllib2.HTTPHandler)
127
url = self.build_url(location, params)
128
request = urllib2.Request(url)
129
request.get_method = lambda: 'PUT'
130
request.add_header('content-type', 'text/json')
131
response = opener.open(request)
132
json_data = self.parse_response(url, response)
134
return json_data['result']
136
def is_logged_in(self):
137
return self.token is not None
139
def login(self, username, password):
140
if not self.is_logged_in():
141
self.token = self.get("login", [username, password])
144
if self.is_logged_in():
73
148
def servers_all(self):
74
return self._get("servers/all", [self.token])
77
def probe_seamicro15k_and_enlist(ip, username, password):
78
api = SeaMicroAPI('http://%s/v0.9/' % ip, username, password)
81
server for _, server in
82
api.servers_all().iteritems()
83
# There are 8 network cards attached to these boxes, we only use NIC 0
85
if server['serverNIC'] != '0'
88
for server in servers:
89
# serverId looks like 63/2, i.e. power id 63, CPU 2. We only want the
90
# system id part of this.
91
[_, system_id] = server['serverId'].split('/')
92
mac = server['serverMacAddr']
149
return self.get("servers/all", [self.token])
152
return self.get("servers", [self.token])
154
def server_index(self, server_id):
155
"""API v0.9 uses arbitrary indexing, this function converts a server
156
id to an index that can be used for detailed outputs & commands.
158
servers = self.servers()['serverId']
159
for idx, name in servers.items():
160
if name == server_id:
164
def power_server(self, server_id, new_status, do_pxe=False, force=False):
165
idx = self.server_index(server_id)
167
raise SeaMicroAPIV09Error(
168
'Failed to retrieve server index, '
169
'invalid server_id: %s' % server_id)
171
location = 'servers/%s' % idx
172
params = ['action=%s' % new_status]
173
if new_status in [POWER_STATUS.ON, POWER_STATUS.RESET]:
175
params.append("using-pxe=true")
177
params.append("using-pxe=false")
178
elif new_status in [POWER_STATUS.OFF]:
180
params.append("force=true")
182
params.append("force=false")
184
raise SeaMicroAPIV09Error('Invalid power action: %s' % new_status)
186
params.append(self.token)
187
self.put(location, params=params)
190
def power_on(self, server_id, do_pxe=False):
191
return self.power_server(server_id, POWER_STATUS.ON, do_pxe=do_pxe)
193
def power_off(self, server_id, force=False):
194
return self.power_server(server_id, POWER_STATUS.OFF, force=force)
196
def reset(self, server_id, do_pxe=False):
197
return self.power_server(server_id, POWER_STATUS.RESET, do_pxe=do_pxe)
200
def get_seamicro15k_api(version, ip, username, password):
201
"""Gets the api client depending on the version.
202
Supports v0.9 and v2.0.
204
:returns: api for version, None if version not supported
206
if version == 'v0.9':
207
api = SeaMicroAPIV09('http://%s/v0.9/' % ip)
209
api.login(username, password)
210
except urllib2.URLError:
211
# Cannot reach using v0.9, might not be supported
214
elif version == 'v2.0':
215
url = 'http://%s' % ip
217
api = seamicro_client.Client(
218
'2', auth_url=url, username=username, password=password)
219
except seamicro_exceptions.ConnectionRefused:
220
# Cannot reach using v2.0, might no be supported
225
def get_seamicro15k_servers(version, ip, username, password):
226
"""Gets a list of tuples containing (server_id, mac_address) from the
227
sm15k api version. Supports v0.9 and v2.0.
229
:returns: list of (server_id, mac_address), None if version not supported
231
api = get_seamicro15k_api(version, ip, username, password)
233
if version == 'v0.9':
235
(server['serverId'].split('/')[0], server['serverMacAddr'])
237
api.servers_all().values()
238
# There are 8 network cards attached to these boxes, we only
239
# use NIC 0 for PXE booting.
240
if server['serverNIC'] == '0'
242
elif version == 'v2.0':
244
(server.id, server.serverMacAddr)
247
# There are 8 network cards attached to these boxes, we only
248
# use NIC 0 for PXE booting.
249
if server.serverNIC == '0'
254
def select_seamicro15k_api_version(power_control):
255
"""Returns the lastest api version to use."""
256
if power_control == 'ipmi':
257
return ['v2.0', 'v0.9']
258
if power_control == 'restapi':
260
if power_control == 'restapi2':
263
'Unsupported power control method: %s.' % power_control)
266
def find_seamicro15k_servers(ip, username, password, power_control):
267
"""Returns the list of servers, using the latest supported api version."""
268
api_versions = select_seamicro15k_api_version(power_control)
269
for version in api_versions:
270
servers = get_seamicro15k_servers(version, ip, username, password)
271
if servers is not None:
273
raise SeaMicroError('Failure to retrieve servers.')
276
def probe_seamicro15k_and_enlist(ip, username, password, power_control=None):
277
power_control = power_control or 'ipmi'
279
servers = find_seamicro15k_servers(ip, username, password, power_control)
280
for system_id, mac in servers:
95
282
'power_address': ip,
96
283
'power_user': username,
97
284
'power_pass': password,
285
'power_control': power_control,
98
286
'system_id': system_id
101
create_node(mac, 'amd64', 'sm15k', params)
289
utils.create_node(mac, 'amd64', 'sm15k', params)
292
def power_control_seamicro15k_v09(ip, username, password, server_id,
293
power_change, retry_count=5, retry_wait=1):
294
server_id = '%s/0' % server_id
295
api = SeaMicroAPIV09('http://%s/v0.9/' % ip)
297
while retry_count > 0:
298
api.login(username, password)
300
if power_change == "on":
301
api.power_on(server_id, do_pxe=True)
302
elif power_change == "off":
303
api.power_off(server_id, force=True)
304
except SeaMicroAPIV09Error as e:
305
# Chance that multiple login's are at once, the api
306
# only supports one at a time. So lets try again after
307
# a second, up to max retry count.
308
if e.response_code == 401:
310
time.sleep(retry_wait)
317
def power_control_seamicro15k_v2(ip, username, password, server_id,
319
api = get_seamicro15k_api('v2.0', ip, username, password)
321
server = api.servers.get(server_id)
322
if power_change == "on":
324
elif power_change == "off":