This file is indexed.

/usr/lib/python2.7/dist-packages/provisioningserver/custom_hardware/seamicro.py is in python-maas-provisioningserver 1.5.4+bzr2294-0ubuntu1.2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# Copyright 2013 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

str = None

__metaclass__ = type
__all__ = [
    'power_control_seamicro15k_v09',
    'power_control_seamicro15k_v2',
    'probe_seamicro15k_and_enlist',
    ]

import httplib
import json
import logging
import time
import urllib2
import urlparse

import provisioningserver.custom_hardware.utils as utils
from seamicroclient.v2 import (
    client as seamicro_client,
    )
from seamicroclient import (
    exceptions as seamicro_exceptions,
    )


logger = logging.getLogger(__name__)


class POWER_STATUS:
    ON = 'Power-On'
    OFF = 'Power-Off'
    RESET = 'Reset'


class SeaMicroError(Exception):
    """Failure talking to a SeaMicro chassis controller. """
    pass


class SeaMicroAPIV09Error(SeaMicroError):
    """Failure talking to a SeaMicro API v0.9. """

    def __init__(self, msg, response_code=None):
        super(SeaMicroAPIV09Error, self).__init__(msg)
        self.response_code = response_code


class SeaMicroAPIV09(object):
    allowed_codes = [httplib.OK, httplib.ACCEPTED, httplib.NOT_MODIFIED]

    def __init__(self, url):
        """
        :param url: The URL of the seamicro chassis, e.g.: http://seamciro/v0.9
        :type url: string
        """
        self.url = url
        self.token = None

    def build_url(self, location, params=None):
        """Builds an order-dependent url, as the SeaMicro chassis
        requires order-dependent parameters.
        """
        if params is None:
            params = []
        params = filter(None, params)
        return urlparse.urljoin(self.url, location) + '?' + '&'.join(params)

    def parse_response(self, url, response):
        """Parses the HTTP response, checking for errors
        from the SeaMicro chassis.
        """
        if response.getcode() not in self.allowed_codes:
            raise SeaMicroAPIV09Error(
                "got response code %s" % response.getcode(),
                response_code=response.getcode())
        text = response.read()

        # Decode the response, it should be json. If not
        # handle that case and set json_data to None, so
        # a SeaMicroAPIV09Error can be raised.
        try:
            json_data = json.loads(text)
        except ValueError:
            json_data = None

        if not json_data:
            raise SeaMicroAPIV09Error(
                'No JSON data found from %s: got %s' % (url, text))
        json_rpc_code = int(json_data['error']['code'])
        if json_rpc_code not in self.allowed_codes:
            raise SeaMicroAPIV09Error(
                'Got JSON RPC error code %d: %s for %s' % (
                    json_rpc_code,
                    httplib.responses.get(json_rpc_code, 'Unknown!'),
                    url),
                response_code=json_rpc_code)
        return json_data

    def get(self, location, params=None):
        """Dispatch a GET request to a SeaMicro chassis.

        The seamicro box has order-dependent HTTP parameters, so we build
        our own get URL, and use a list vs. a dict for data, as the order is
        implicit.
        """
        url = self.build_url(location, params)
        response = urllib2.urlopen(url)
        json_data = self.parse_response(url, response)

        return json_data['result']

    def put(self, location, params=None):
        """Dispatch a PUT request to a SeaMicro chassis.

        The seamicro box has order-dependent HTTP parameters, so we build
        our own get URL, and use a list vs. a dict for data, as the order is
        implicit.
        """
        opener = urllib2.build_opener(urllib2.HTTPHandler)
        url = self.build_url(location, params)
        request = urllib2.Request(url)
        request.get_method = lambda: 'PUT'
        request.add_header('content-type', 'text/json')
        response = opener.open(request)
        json_data = self.parse_response(url, response)

        return json_data['result']

    def is_logged_in(self):
        return self.token is not None

    def login(self, username, password):
        if not self.is_logged_in():
            self.token = self.get("login", [username, password])

    def logout(self):
        if self.is_logged_in():
            self.get("logout")
            self.token = None

    def servers_all(self):
        return self.get("servers/all", [self.token])

    def servers(self):
        return self.get("servers", [self.token])

    def server_index(self, server_id):
        """API v0.9 uses arbitrary indexing, this function converts a server
        id to an index that can be used for detailed outputs & commands.
        """
        servers = self.servers()['serverId']
        for idx, name in servers.items():
            if name == server_id:
                return idx
        return None

    def power_server(self, server_id, new_status, do_pxe=False, force=False):
        idx = self.server_index(server_id)
        if idx is None:
            raise SeaMicroAPIV09Error(
                'Failed to retrieve server index, '
                'invalid server_id: %s' % server_id)

        location = 'servers/%s' % idx
        params = ['action=%s' % new_status]
        if new_status in [POWER_STATUS.ON, POWER_STATUS.RESET]:
            if do_pxe:
                params.append("using-pxe=true")
            else:
                params.append("using-pxe=false")
        elif new_status in [POWER_STATUS.OFF]:
            if force:
                params.append("force=true")
            else:
                params.append("force=false")
        else:
            raise SeaMicroAPIV09Error('Invalid power action: %s' % new_status)

        params.append(self.token)
        self.put(location, params=params)
        return True

    def power_on(self, server_id, do_pxe=False):
        return self.power_server(server_id, POWER_STATUS.ON, do_pxe=do_pxe)

    def power_off(self, server_id, force=False):
        return self.power_server(server_id, POWER_STATUS.OFF, force=force)

    def reset(self, server_id, do_pxe=False):
        return self.power_server(server_id, POWER_STATUS.RESET, do_pxe=do_pxe)


def get_seamicro15k_api(version, ip, username, password):
    """Gets the api client depending on the version.
    Supports v0.9 and v2.0.

    :returns: api for version, None if version not supported
    """
    if version == 'v0.9':
        api = SeaMicroAPIV09('http://%s/v0.9/' % ip)
        try:
            api.login(username, password)
        except urllib2.URLError:
            # Cannot reach using v0.9, might not be supported
            return None
        return api
    elif version == 'v2.0':
        url = 'http://%s/v2.0' % ip
        try:
            api = seamicro_client.Client(
                auth_url=url, username=username, password=password)
        except seamicro_exceptions.ConnectionRefused:
            # Cannot reach using v2.0, might no be supported
            return None
        return api


def get_seamicro15k_servers(version, ip, username, password):
    """Gets a list of tuples containing (server_id, mac_address) from the
    sm15k api version. Supports v0.9 and v2.0.

    :returns: list of (server_id, mac_address), None if version not supported
    """
    api = get_seamicro15k_api(version, ip, username, password)
    if api:
        if version == 'v0.9':
            return (
                (server['serverId'].split('/')[0], server['serverMacAddr'])
                for server in
                api.servers_all().values()
                # There are 8 network cards attached to these boxes, we only
                # use NIC 0 for PXE booting.
                if server['serverNIC'] == '0'
            )
        elif version == 'v2.0':
            servers = []
            for server in api.servers.list():
                id = server.id.split('/')[0]
                macs = [nic['macAddr'] for nic in server.nic.values()]
                servers.append((id, macs))
            return servers
    return None


def select_seamicro15k_api_version(power_control):
    """Returns the lastest api version to use."""
    if power_control == 'ipmi':
        return ['v2.0', 'v0.9']
    if power_control == 'restapi':
        return ['v0.9']
    if power_control == 'restapi2':
        return ['v2.0']
    raise SeaMicroError(
        'Unsupported power control method: %s.' % power_control)


def find_seamicro15k_servers(ip, username, password, power_control):
    """Returns the list of servers, using the latest supported api version."""
    api_versions = select_seamicro15k_api_version(power_control)
    for version in api_versions:
        servers = get_seamicro15k_servers(version, ip, username, password)
        if servers is not None:
            return servers
    raise SeaMicroError('Failure to retrieve servers.')


def probe_seamicro15k_and_enlist(ip, username, password, power_control=None):
    power_control = power_control or 'ipmi'

    servers = find_seamicro15k_servers(ip, username, password, power_control)
    for system_id, mac in servers:
        params = {
            'power_address': ip,
            'power_user': username,
            'power_pass': password,
            'power_control': power_control,
            'system_id': system_id
        }

        utils.create_node(mac, 'amd64', 'sm15k', params)


def power_control_seamicro15k_v09(ip, username, password, server_id,
                                  power_change, retry_count=5, retry_wait=1):
    server_id = '%s/0' % server_id
    api = SeaMicroAPIV09('http://%s/v0.9/' % ip)

    while retry_count > 0:
        api.login(username, password)
        try:
            if power_change == "on":
                api.power_on(server_id, do_pxe=True)
            elif power_change == "off":
                api.power_off(server_id, force=True)
        except SeaMicroAPIV09Error as e:
            # Chance that multiple login's are at once, the api
            # only supports one at a time. So lets try again after
            # a second, up to max retry count.
            if e.response_code == 401:
                retry_count -= 1
                time.sleep(retry_wait)
                continue
            else:
                raise
        break


def power_control_seamicro15k_v2(ip, username, password, server_id,
                                 power_change):
    server_id = '%s/0' % server_id
    api = get_seamicro15k_api('v2.0', ip, username, password)
    if api:
        server = api.servers.get(server_id)
        if power_change == "on":
            server.power_on(using_pxe=True)
        elif power_change == "off":
            server.power_off(force=True)