Esempio n. 1
0
    def test_read_custom_api_url(self, read_file_mock):
        """ Test API URL reading """

        # No config
        with patch('os.getenv', return_value=None):
            read_file_mock.side_effect = IOError()
            self.assertTrue(helpers.read_custom_api_url() is None)

        # Only File
        with patch('os.getenv', return_value=None):
            read_file_mock.side_effect = None
            read_file_mock.return_value = 'API_URL_CUSTOM'
            self.assertEqual('API_URL_CUSTOM', helpers.read_custom_api_url())

        # Only env variable
        with patch('os.getenv', return_value='API_URL_2'):
            read_file_mock.side_effect = IOError()
            self.assertEqual('API_URL_2', helpers.read_custom_api_url())

        # File priority over env variable
        with patch('os.getenv', return_value='API_URL_2'):
            read_file_mock.side_effect = None
            read_file_mock.return_value = 'API_URL_CUSTOM'
            self.assertEqual('API_URL_CUSTOM', helpers.read_custom_api_url())
Esempio n. 2
0
    def test_read_custom_api_url(self, read_file_mock):
        """ Test API URL reading """

        # No config
        with patch('os.getenv', return_value=None):
            read_file_mock.side_effect = IOError()
            self.assertTrue(helpers.read_custom_api_url() is None)

        # Only File
        with patch('os.getenv', return_value=None):
            read_file_mock.side_effect = None
            read_file_mock.return_value = 'API_URL_CUSTOM'
            self.assertEqual('API_URL_CUSTOM', helpers.read_custom_api_url())

        # Only env variable
        with patch('os.getenv', return_value='API_URL_2'):
            read_file_mock.side_effect = IOError()
            self.assertEqual('API_URL_2', helpers.read_custom_api_url())

        # File priority over env variable
        with patch('os.getenv', return_value='API_URL_2'):
            read_file_mock.side_effect = None
            read_file_mock.return_value = 'API_URL_CUSTOM'
            self.assertEqual('API_URL_CUSTOM', helpers.read_custom_api_url())
Esempio n. 3
0
"""

import requests
import json
from requests.auth import HTTPBasicAuth
try:
    # pylint: disable=import-error,no-name-in-module
    from urllib.parse import urljoin
except ImportError:  # pragma: no cover
    # pylint: disable=import-error,no-name-in-module
    from urlparse import urljoin
from iotlabcli import helpers


API_URL = helpers.read_custom_api_url() or 'https://www.iot-lab.info/rest/'


# pylint: disable=maybe-no-member,no-member
class Api(object):
    """ IoT-Lab REST API """
    _cache = {}

    def __init__(self, username, password, url=API_URL):
        """
        :param username: username for Basic password auth
        :param password: password for Basic auth
        :param url: url of API.
        """
        self.url = url
        self.auth = HTTPBasicAuth(username, password)
Esempio n. 4
0
class Api(object):  # pylint:disable=too-many-public-methods
    """ IoT-Lab REST API """
    _cache = {}
    url = helpers.read_custom_api_url() or 'https://www.iot-lab.info/rest/'

    def __init__(self, username, password):
        """
        :param username: username for Basic password auth
        :param password: password for Basic auth
        :param url: url of API.
        """
        self.auth = HTTPBasicAuth(username, password)

    def get_resources(self, list_id=False, site=None, **selections):
        """ Get testbed resources description

        :param list_id: return result in 'exp_list' format '3-12+35'
        :param site: restrict to site
        :param **selections: other selections than site
        """
        if site:
            selections['site'] = site

        url = 'experiments?%s' % ('id' if list_id else 'resources')
        for selection, value in sorted(selections.items()):
            url += '&{0}={1}'.format(selection, value)
        return self.method(url)

    def submit_experiment(self, files):
        """ Submit user experiment

        :param files: experiment description and firmware(s)
        :type files: dictionnary
        :returns JSONObject
        """
        return self.method('experiments', 'post', files=files)

    def get_experiments(self, state='Running', limit=0, offset=0):
        """ Get user's experiment
        :returns JSONObject
        """
        queryset = 'state=%s&limit=%u&offset=%u' % (state, limit, offset)
        return self.method('experiments?%s' % queryset)

    def get_experiment_info(self, expid, option=''):
        """ Get user experiment description.
        :param expid: experiment id submission (e.g. OAR scheduler)
        :param option: Restrict to some values:
            * '':          experiment submission
            * 'resources': resources list
            * 'id':        resources id list: (1-34+72 format)
            * 'state':     experiment state
            * 'data':      experiment tar.gz with description and firmwares
            * 'start':     expected start time
        """
        assert option in ('', 'resources', 'id', 'state', 'data', 'start')

        url = 'experiments/%s' % expid
        if option:
            url += '?%s' % option
        return self.method(url, raw=(option == 'data'))

    @classmethod
    def get_any_experiment_state(cls, expid, username):
        """Get any experiment state."""

        url = 'experiments/{expid}?anystate&user={username}'
        url = url.format(expid=expid, username=username)

        # Get value
        api = cls(None, None)
        return api.method(url)

    def stop_experiment(self, expid):
        """ Stop user experiment.

        :param id: experiment id submission (e.g. OAR scheduler)
        """
        return self.method('experiments/%s' % expid, 'delete')

    def reload_experiment(self, expid, exp_json=None):
        """Reload user experiment.

        :param expid: experiment id submission (e.g. OAR scheduler)
        :param exp_json: experiment duration and reservation configuration
        :returns JSONObject
        """
        url = 'experiments/%d?reload' % expid
        return self.method(url, 'post', json=exp_json)

    # Node commands

    def node_command(self, command, expid, nodes=(), option=None):
        """ Lanch 'command' on user experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        :param opt: additional string to pass as option in the query string
        :returns: dict
        """
        option = option or ''  # case of None
        return self.method(
            'experiments/%s/nodes?%s%s' % (expid, command, option),
            'post', json=nodes)

    def node_update(self, expid, files):
        """ Launch update command (flash firmware) on user
        experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param files: nodes list description and firmware
        :type files: dict
        :returns: dict
        """
        return self.method('experiments/%s/nodes?update' % expid,
                           'post', files=files)

    def node_profile_load(self, expid, files):
        """Update profile with profile json on user
        experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param files: nodes list description and firmware
        :type files: dict
        :returns: dict
        """
        return self.method('experiments/%s/nodes?profile-load' % expid,
                           'post', files=files)

    # script
    def script_command(self, expid, command, files=None, json=None):
        """Execute scripts on sites.

        :param expid: experiment id submission (e.g. OAR scheduler)
        :param command: in ('run', 'kill', 'status')
        :param files: 'run' only: script-site association and scripts content
        :param json: 'kill/status' only: sites list, may be empty for all sites
        """
        # Only json or files and for the correct command (inverted checks)
        assert json is not None or command in ('run',)
        assert files is not None or command in ('kill', 'status',)

        url = 'experiments/%s/script?%s' % (expid, command)
        return self.method(url, 'post', files=files, json=json)

    # Profile methods

    def get_profiles(self, archi=None):
        """ Get user's list profile description

        :returns JSONObject
        """
        url = 'profiles'
        if archi is not None:
            url += '?archi={0}'.format(archi)
        return self.method(url)

    def get_profile(self, name):
        """ Get user profile description.

        :param name: profile name
        :type name: string
        :returns JSONObject
        """
        return self.method('profiles/%s' % name)

    def add_profile(self, name, profile):
        """ Add user profile

        :param profile: profile description
        :type profile: JSONObject.
        """
        # dict has no __dict__ and load_profile gives a dict
        # requests wants a 'simple' type like dict
        profile = profile if isinstance(profile, dict) else profile.__dict__
        ret = self.method('profiles/%s' % name, 'post', json=profile)
        return ret

    def del_profile(self, name):
        """ Delete user profile

        :param profile_name: name
        :type profile_name: string
        """
        ret = self.method('profiles/%s' % name, 'delete')
        return ret

    def check_credential(self):
        """ Check that the credentials are valid """
        try:
            self.method('users/%s?login' % self.auth.username, raw=True)
            return True
        except HTTPError as err:
            if err.code == 401:
                return False
            raise  # pragma no cover

    # robot

    def robot_command(self, command, expid, nodes=()):
        """Run 'status' on user experiment robot list nodes.

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        """
        assert command in ('status',)
        return self.method('experiments/%s/robots' % expid, 'post', json=nodes)

    def robot_update_mobility(self, expid, name, site, nodes=()):
        """Update mobility on user experiment robot list nodes.

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        """
        url = 'experiments/%s/robots?mobility' % expid
        url += '&name=%s&site=%s' % (name, site)
        return self.method(url, 'post', json=nodes)

    @classmethod
    def mobility_predefined_list(cls):
        """List predefined mobilities."""
        return cls._get_with_cache('robots/mobility')

    def mobility_user_list(self):
        """List user mobilities."""
        return self.method('robots/mobility?user')

    def mobility_user_get(self, name, site):
        """Get user mobilities."""
        return self.method('robots/mobility/{site}/{name}'.format(name=name,
                                                                  site=site))

    def method(self, url, method='get',  # pylint:disable=too-many-arguments
               json=None, files=None, raw=False):
        """Call http `method` on iot-lab-url/'url'.

        :param url: url of API.
        :param method: request method
        :param json: send as 'post' json encoded data
        :param files: send as 'post' multipart data
        :param raw: Should data be loaded as json or not
        """
        assert method in ('get', 'post', 'delete')
        assert (method == 'post') or (files is None and json is None)

        _url = urljoin(self.url, url)

        req = self._request(_url, method, auth=self.auth,
                            json=json, files=files)
        if requests.codes.ok == req.status_code:
            return req.content if raw else req.json()
        self._raise_http_error(_url, req)

    @staticmethod
    def _request(url, method, **kwargs):
        """ Call http `method` on 'url'

        :param url: url of API.
        :param method: request method
        :param **kwargs: requests.request additional arguments """
        try:
            return requests.request(method, url, **kwargs)
        except:  # show issue with old requests versions
            raise RuntimeError(sys.exc_info())

    @staticmethod
    def _raise_http_error(url, req):
        """ Raises HTTP error for 'url' and 'req' """
        # Indent req.text to pretty print it later
        indented_lines = ['\t' + l for l in req.text.splitlines(True)]
        msg = '\n' + ''.join(indented_lines)
        raise HTTPError(url, req.status_code, msg, req.headers, None)

    @classmethod
    def get_robot_mapfile(cls, site, mapfile):
        """ Download robot mapfile.

        :params site: Map info for site
        :params mapfile: select type in ('mapconfig', 'mapimage', 'dockconfig')
        :returns: Image content or json loaded structure
        """
        assert mapfile in ('mapconfig', 'mapimage', 'dockconfig')
        raw = mapfile in ('mapimage',)

        api = cls(None, None)
        url = 'robots/mobility/map/%s?%s' % (site, mapfile)
        return api.method(url, raw=raw)

    @classmethod
    def get_sites(cls):
        """ Get testbed sites description
        May be run unauthicated

        :returns JSONObject
        """
        return cls._get_with_cache('experiments?sites')

    @classmethod
    def _get_with_cache(cls, url):
        """ Get resource from either cache or rest
        :returns JSONObject
        """
        try:
            return cls._cache[url]
        except KeyError:
            api = cls(None, None)  # unauthenticated request
            return cls._cache.setdefault(url, api.method(url))
Esempio n. 5
0
first parameter to the function.

"""

import requests
import json
from requests.auth import HTTPBasicAuth
try:
    # pylint: disable=import-error,no-name-in-module
    from urllib.parse import urljoin
except ImportError:  # pragma: no cover
    # pylint: disable=import-error,no-name-in-module
    from urlparse import urljoin
from iotlabcli import helpers

API_URL = helpers.read_custom_api_url() or 'https://www.iot-lab.info/rest/'


# pylint: disable=maybe-no-member,no-member
class Api(object):
    """ IoT-Lab REST API """
    _cache = {}

    def __init__(self, username, password, url=API_URL):
        """
        :param username: username for Basic password auth
        :param password: password for Basic auth
        :param url: url of API.
        """
        self.url = url
        self.auth = HTTPBasicAuth(username, password)
Esempio n. 6
0
def get_api_url():
    """ Return API url """
    return read_custom_api_url() or API_URL
Esempio n. 7
0
class Api():  # pylint:disable=too-many-public-methods
    """ IoT-Lab REST API """
    _cache = {}
    url = helpers.read_custom_api_url() or 'https://www.iot-lab.info/api/'

    def __init__(self, username, password):
        """
        :param username: username for Basic password auth
        :param password: password for Basic auth
        :param url: url of API.
        """
        self.auth = HTTPBasicAuth(username, password)

    def get_sites_details(self):
        """ Get testbed sites details """
        return self.method('sites/details')

    def get_nodes(self, list_id=False, site=None, **selections):
        """ Get testbed nodes description

        :param list_id: return result in 'exp_list' format '3-12+35'
        :param site: restrict to site
        :param **selections: other selections than site
        """
        if site:
            selections['site'] = site

        url = f"nodes{'/ids' if list_id else ''}"
        if selections:
            # the order of parameters in the encoded string
            # will match the order of tuples list
            url += '?' + urlencode(sorted(list(selections.items())))
        return self.method(url)

    def submit_experiment(self, files):
        """ Submit user experiment

        :param files: experiment description and firmware(s)
        :type files: dictionnary
        :returns JSONObject
        """
        return self.method('experiments', 'post', files=files)

    def get_experiments(self, state='Running', limit=0, offset=0):
        """ Get user's experiment
        :returns JSONObject
        """
        queryset = f'state={state}&limit={limit}&offset={offset}'
        return self.method(f'experiments?{queryset}')

    def get_running_experiments(self):
        """ Get testbed running experiments """
        return self.method('experiments/running')

    def get_experiment_info(self, expid, option=''):
        """ Get user experiment description.
        :param expid: experiment id submission (e.g. OAR scheduler)
        :param option: Restrict to some values:
            * '':            experiment submission
            * 'nodes':       nodes list
            * 'nodes_ids':   nodes id list: (1-34+72 format)
            * 'data':        experiment tar.gz with description and firmwares
            * 'deployment':  deployment info
        """
        assert option in ('', 'nodes', 'nodes_ids', 'data', 'deployment')

        url = f'experiments/{expid}'
        if option:
            url += f'/{option}'
        return self.method(url, raw=(option == 'data'))

    def stop_experiment(self, expid):
        """ Stop user experiment.

        :param id: experiment id submission (e.g. OAR scheduler)
        """
        return self.method(f'experiments/{expid}', 'delete')

    def reload_experiment(self, expid, exp_json=None):
        """Reload user experiment.

        :param expid: experiment id submission (e.g. OAR scheduler)
        :param exp_json: experiment duration and reservation configuration
        :returns JSONObject
        """
        url = f'experiments/{expid}/reload'
        return self.method(url, 'post', json=exp_json)

    # Node commands

    def node_command(self, command, expid, nodes=(), option=None):
        """ Lanch 'command' on user experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        :param opt: additional string to pass as option in the url
        :returns: dict
        """
        url = f'experiments/{expid}/nodes/{command}'
        if option:
            url += f'/{option}'
        return self.method(url, 'post', json=nodes)

    def node_update(self, expid, files, binary=False):
        """ Launch update command (flash firmware) on user
        experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param files: nodes list description and firmware
        :type files: dict
        :returns: dict
        """
        url = f'experiments/{expid}/nodes/flash'
        if binary:
            url += '/binary'
        return self.method(url, 'post', files=files)

    def node_profile_load(self, expid, files):
        """Update profile with profile json on user
        experiment list nodes

        :param id: experiment id submission (e.g. OAR scheduler)
        :param files: nodes list description and firmware
        :type files: dict
        :returns: dict
        """
        return self.method(f'experiments/{expid}/nodes/monitoring',
                           'post', files=files)

    # script
    def script_command(self, expid, command, files=None, json=None):
        """Execute scripts on sites.

        :param expid: experiment id submission (e.g. OAR scheduler)
        :param command: in ('run', 'kill', 'status')
        :param files: 'run' only: script-site association and scripts content
        :param json: 'kill/status' only: sites list, may be empty for all sites
        """
        # Only json or files and for the correct command (inverted checks)
        assert json is not None or command in ('run',)
        assert files is not None or command in ('kill', 'status',)

        url = f'experiments/{expid}/scripts/{command}'
        return self.method(url, 'post', files=files, json=json)

    # Profile methods

    def get_profiles(self, archi=None):
        """ Get user's list profile description

        :returns JSONObject
        """
        url = 'monitoring'
        if archi is not None:
            url += f'?archi={archi}'
        return self.method(url)

    def get_profile(self, name):
        """ Get user profile description.

        :param name: profile name
        :type name: string
        :returns JSONObject
        """
        return self.method(f'monitoring/{name}')

    def add_profile(self, profile):
        """ Add user profile

        :param profile: profile description
        :type profile: JSONObject.
        """
        # dict has no __dict__ and load_profile gives a dict
        # requests wants a 'simple' type like dict
        profile = profile if isinstance(profile, dict) else profile.__dict__
        ret = self.method('monitoring', 'post', json=profile)
        return ret

    def del_profile(self, name):
        """ Delete user profile

        :param profile_name: name
        :type profile_name: string
        """
        ret = self.method(f'monitoring/{name}', 'delete')
        return ret

    def check_credential(self):
        """ Check that the credentials are valid """
        try:
            self.method('user')
            return True
        except HTTPError as err:
            if err.code == 401:
                return False
            raise  # pragma no cover

    # ssh keys api

    def get_ssh_keys(self):
        """ Get user's registered ssh keys """
        ret = self.method('user/keys')
        return ret

    def set_ssh_keys(self, ssh_keys_json):
        """ Set user's ssh keys """
        self.method('user/keys', 'post', json=ssh_keys_json, raw=True)

    # robot

    def robot_command(self, command, expid, nodes=()):
        """Run 'status' on user experiment robot list nodes.

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        """
        assert command in ('status',)
        return self.method(f'experiments/{expid}/robots/{command}',
                           'post', json=nodes)

    def robot_update_mobility(self, expid, name, nodes=()):
        """Update mobility on user experiment robot list nodes.

        :param id: experiment id submission (e.g. OAR scheduler)
        :param nodes: list of nodes, if empty apply on all nodes
        """
        url = f'experiments/{expid}/robots/mobility/{name}'
        return self.method(url, 'post', json=nodes)

    @classmethod
    def get_robot_mapfile(cls, site, mapfile):
        """ Download robot mapfile.

        :params site: Map info for site
        :params mapfile: select type in ('mapconfig', 'mapimage', 'dockconfig')
        :returns: Image content or json loaded structure
        """
        assert mapfile in ('map/config', 'map/image', 'dock/config')
        raw = mapfile in ('map/image',)

        api = cls(None, None)
        url = f'robots/{site}/{mapfile}'
        return api.method(url, raw=raw)

    def get_circuits(self, **selections):
        """List circuits mobilities."""
        url = 'mobilities/circuits'
        if selections:
            # the order of parameters in the encoded string
            # will match the order of tuples list
            url += '?' + urlencode(sorted(list(selections.items())))
        return self.method(url)

    def get_circuit(self, name):
        """Get user mobilities."""
        return self.method(f'mobilities/circuits/{name}')

    def method(self, url, method='get',  # pylint:disable=too-many-arguments
               json=None, files=None, raw=False):
        """Call http `method` on iot-lab-url/'url'.

        :param url: url of API.
        :param method: request method
        :param json: send as 'post' json encoded data
        :param files: send as 'post' multipart data
        :param raw: Should data be loaded as json or not
        """
        assert method in ('get', 'post', 'delete')
        assert (method == 'post') or (files is None and json is None)

        _url = urljoin(self.url, url)

        req = self._request(_url, method, auth=self.auth,
                            json=json, files=files)
        if requests.codes.ok == req.status_code:
            return req.content if raw else req.json()
        if requests.codes.no_content == req.status_code:
            return None
        return self._raise_http_error(_url, req)

    @staticmethod
    def _request(url, method, **kwargs):
        """ Call http `method` on 'url'

        :param url: url of API.
        :param method: request method
        :param **kwargs: requests.request additional arguments """
        try:
            return requests.request(method, url, **kwargs)
        except Exception:  # show issue with old requests versions
            raise RuntimeError(sys.exc_info())

    @staticmethod
    def _raise_http_error(url, req):
        """ Raises HTTP error for 'url' and 'req' """
        # Indent req.text to pretty print it later
        indented_lines = ['\t' + line for line in req.text.splitlines(True)]
        msg = '\n' + ''.join(indented_lines)
        raise HTTPError(url, req.status_code, msg, req.headers, None)

    @classmethod
    def get_sites(cls):
        """ Get testbed sites description
        May be run unauthicated

        :returns JSONObject
        """
        return cls._get_with_cache('sites')

    @classmethod
    def _get_with_cache(cls, url):
        """ Get resource from either cache or rest
        :returns JSONObject
        """
        try:
            return cls._cache[url]
        except KeyError:
            api = cls(None, None)  # unauthenticated request
            return cls._cache.setdefault(url, api.method(url))