예제 #1
0
    def from_response(cls, body, identity):
        """
        Create a host object from an API response.

        :param body: The raw API response text.
        :param identity: This host's identity.
        :return: An object representing the host.
        :raises ValueError: If the response is empty or malformed.
        :raises RuntimeError: If the response indicates the API request failed.
        """
        if not body:
            raise ValueError('Cannot construct host from empty response')

        try:
            root = cElementTree.fromstring('<root>' + body + '</root>')

            if root.find('status').text != 'success':
                message = root.find('statusmsg')
                raise RuntimeError(
                    'Response indicates failed API call: {0}'.format(
                        message.text if message else 'unspecified error'))

            return Host(identity,
                        root.find('hostname').text,
                        root.find('ipaddress').text,
                        root.find('vmstat').text == 'online',
                        Resource.from_response(root.find('mem').text),
                        Resource.from_response(root.find('hdd').text),
                        Resource.from_response(root.find('bw').text),
                        root.find('ipaddr').text.split(','))
        except ParseError as e:
            raise ValueError('Host response is malformed: {0}'.format(e))
        except AttributeError as e:
            raise ValueError(
                'Host response is missing an attribute: {0}'.format(e))
예제 #2
0
class TestHost(unittest.TestCase):
    _VENDOR_ENDPOINT = 'https://vpscp.ramnode.com'
    IDENTITY = HostIdentity('host-name', 'host-key', 'host-hash',
                            Vendor('vendor-name', _VENDOR_ENDPOINT))
    _FQDN = 'host.example.com'
    _PRIMARY_IP = '8.8.8.8'
    _IS_ONLINE = True
    _MEMORY = Resource(100, 100)
    _STORAGE = Resource(200, 200)
    _BANDWIDTH = Resource(300, 300)
    _IP_ADDRESSES = [_PRIMARY_IP, '10.10.10.10']
    _XML = '''<ipaddr>{0}</ipaddr>
<hdd>200,100,100,50</hdd>
<bw>400,200,200,50</bw>
<mem>600,300,300,50</mem>
<status>{1}</status>
<statusmsg></statusmsg>
<hostname>{2}</hostname>
<ipaddress>{3}</ipaddress>
<vmstat>{4}</vmstat>'''
    _XML_VALID = _XML.format(','.join(_IP_ADDRESSES), 'success', _FQDN,
                             _PRIMARY_IP,
                             'online' if _IS_ONLINE else 'offline')
    HOST = Host(IDENTITY, _FQDN, _PRIMARY_IP, _IS_ONLINE, _MEMORY, _STORAGE,
                _BANDWIDTH, _IP_ADDRESSES)

    @classmethod
    def setUpClass(cls):
        cls.host = Host(cls.IDENTITY, cls._FQDN, cls._PRIMARY_IP,
                        cls._IS_ONLINE, cls._MEMORY, cls._STORAGE,
                        cls._BANDWIDTH, cls._IP_ADDRESSES)

    @staticmethod
    def _make_host(identity=IDENTITY,
                   fqdn=_FQDN,
                   primary_ip=_PRIMARY_IP,
                   is_online=_IS_ONLINE,
                   memory=_MEMORY,
                   storage=_STORAGE,
                   bandwidth=_BANDWIDTH,
                   ip_addresses=_IP_ADDRESSES):
        return Host(identity, fqdn, primary_ip, is_online, memory, storage,
                    bandwidth, ip_addresses)

    @staticmethod
    def add_response(verb=responses.GET,
                     url=_VENDOR_ENDPOINT + '/api/client/command.php',
                     body=_XML_VALID,
                     status=200):
        responses.add(verb, url, body, status=status)

    def test_action_none(self):
        with self.assertRaises(ValueError):
            self.host.action(None)

    def test_action_invalid(self):
        with self.assertRaises(ValueError):
            self.host.action('invalid')

    def test_boot(self):
        with patch.object(Host, 'action') as action:
            self.host.boot()
        action.assert_called_once_with('boot')

    def test_reboot(self):
        with patch.object(Host, 'action') as action:
            self.host.reboot()
        action.assert_called_once_with('reboot')

    def test_shutdown(self):
        with patch.object(Host, 'action') as action:
            self.host.shutdown()
        action.assert_called_once_with('shutdown')

    @responses.activate
    def test_request_from_identity_denied(self):
        self.add_response(status=403)
        with self.assertRaises(RuntimeError):
            Host.request_from_identity(self.IDENTITY)

    @responses.activate
    def test_request_from_identity(self):
        responses.add(responses.GET,
                      self._VENDOR_ENDPOINT + '/api/client/command.php',
                      status=200,
                      body=self._XML_VALID)
        self.assertEqual(Host.request_from_identity(self.IDENTITY), self.host)

    def test_is_offline_false(self):
        self.assertFalse(self.host.is_offline)

    def test_is_offline_true(self):
        self.assertTrue(self._make_host(is_online=False).is_offline)

    def test_from_response_none(self):
        with self.assertRaises(ValueError):
            Host.from_response(None, self.IDENTITY)

    def test_from_response_malformed(self):
        with self.assertRaises(ValueError):
            Host.from_response('><', self.IDENTITY)

    def test_from_response_api_failure(self):
        with self.assertRaises(RuntimeError):
            Host.from_response(
                self._XML.format(','.join(self._IP_ADDRESSES), 'failure',
                                 self._FQDN, self._PRIMARY_IP,
                                 'online' if self._IS_ONLINE else 'offline'),
                self.IDENTITY)

    def test_from_response_missing_attribute(self):
        with self.assertRaises(ValueError):
            Host.from_response('<root/>', self.IDENTITY)

    def test_from_response(self):
        self.assertEqual(Host.from_response(self._XML_VALID, self.IDENTITY),
                         self.host)

    def test_str(self):
        self.assertEqual(str(self.host),
                         '{0}({1})'.format(Host.__name__, self._FQDN))
예제 #3
0
 def test_eq_false_free(self):
     self.assertNotEqual(self.resource, Resource(100, 100))
예제 #4
0
 def test_str(self):
     resource = Resource(6155997184, 6728904704)
     self.assertEqual(str(resource),
                      'Resource(6155997184 bytes used (47.78%), 6728904704 '
                      'bytes free (52.22%))')
예제 #5
0
 def test_from_response(self):
     self.assertEqual(Resource(6155997184, 6728904704),
                      Resource.from_response('12884901888,6155997184,'
                                             '6728904704,48'))
예제 #6
0
 def test_eq_false_used(self):
     self.assertNotEqual(self.resource, Resource(50, 50))
예제 #7
0
 def test_from_response_float(self):
     with self.assertRaises(ValueError):
         Resource.from_response('12884901.888,6155997184,6728904704,48')
예제 #8
0
 def test_from_response_string(self):
     with self.assertRaises(ValueError):
         Resource.from_response('12884901888,surprise,6728904704,48')
예제 #9
0
 def test_from_response_too_few_fragments(self):
     with self.assertRaises(ValueError):
         Resource.from_response('12884901888,6155997184,6728904704')
예제 #10
0
 def test_from_response_none(self):
     with self.assertRaises(ValueError):
         Resource.from_response(None)
예제 #11
0
 def test_zero_total_bytes(self):
     self.assertEqual(Resource(0, 0).used_percentage, 1)
예제 #12
0
 def test_percentages_sum_to_one(self):
     resource = Resource(14682693209, 522188218791)
     self.assertEqual(resource.used_percentage + resource.free_percentage,
                      1)
예제 #13
0
 def setUpClass(cls):
     cls.resource = Resource(cls._USED_BYTES, cls._FREE_BYTES)