Przeglądaj źródła

Add licensing server API bindings.

Nashwan Azhari 7 lat temu
rodzic
commit
2d724407c4
2 zmienionych plików z 182 dodań i 0 usunięć
  1. 0 0
      coriolis/licensing/__init__.py
  2. 182 0
      coriolis/licensing/client.py

+ 0 - 0
coriolis/licensing/__init__.py


+ 182 - 0
coriolis/licensing/client.py

@@ -0,0 +1,182 @@
+# Copyright 2019 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import json
+import os
+import requests
+
+from coriolis import exception
+from coriolis import utils
+from oslo_log import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+RESERVATION_TYPE_REPLICA = "replica"
+RESERVATION_TYPE_MIGRATION = "migration"
+
+
+class LicensingClient(object):
+    """ Class for accessing the Coriolis licensing server API. """
+
+    def __init__(self, base_url, allow_untrusted=False):
+        """ :param base_url: URL for the API service, including scheme """
+        self._base_url = base_url.rstrip('/')
+        self._verify = not allow_untrusted
+
+    @classmethod
+    def from_env(cls):
+        """ Retuns a `LicensingClient` object instatiated using the
+        following env vars:
+        LICENSING_SERVER_BASE_URL="https://10.7.2.3:37667/v1"
+        LICENSING_SERVER_ALLOW_UNTRUSTED="<set to anything>"
+        Returns None if 'LICENSING_SERVER_BASE_URL' is not defined.
+        """
+        base_url = os.environ.get("LICENSING_SERVER_BASE_URL")
+        if not base_url:
+            LOG.warn(
+                "No 'LICENSING_SERVER_BASE_URL' env var present. Cannot "
+                "instantiate licensing client.")
+            return None
+        allow_untrusted = os.environ.get(
+            "LICENSING_SERVER_ALLOW_UNTRUSTED", False)
+
+        # try out client:
+        client = cls(base_url, allow_untrusted=allow_untrusted)
+        client.get_licence_status()
+
+        return client
+
+    def _get_url_for_resource(self, resource):
+        """ Provides full URL for subresource.
+        Ex: "licences" -> "http://$host:$port/v1/licences"
+        """
+        return "%s/%s" % (self._base_url, resource.strip('/'))
+
+    @utils.retry_on_error()
+    def _do_req(
+            self, method_name, resource, body=None,
+            response_key=None, raw_response=False):
+        method = getattr(requests, method_name.lower(), None)
+        if not method:
+            raise ValueError("No such HTTP method '%s'" % method_name)
+
+        url = self._get_url_for_resource(resource)
+
+        kwargs = {"verify": self._verify}
+        if body:
+            if not isinstance(body, (str, bytes)):
+                body = json.dumps(body)
+            kwargs["data"] = body
+
+        LOG.debug(
+            "Making '%s' call to licensing server at '%s' with body: %s",
+            method_name, url, kwargs.get('data'))
+        resp = method(url, **kwargs)
+
+        if raw_response:
+            return resp
+
+        if not resp.ok:
+            # try to extract error from licensing server:
+            error = None
+            try:
+                error = resp.json().get('error', {})
+            except (Exception, KeyboardInterrupt):
+                LOG.debug(
+                    "Exception occured during error extraction from licensing "
+                    "response: '%s'\nException:\n%s",
+                    resp.text, utils.get_exception_details())
+            if error and all([x in error for x in ['code', 'message']]):
+                raise exception.Conflict(
+                    message=error['message'],
+                    code=int(error['code']))
+            else:
+                resp.raise_for_status()
+
+        resp_data = resp.json()
+        if response_key:
+            if response_key not in resp_data:
+                raise ValueError(
+                    "No response key '%s' in response body: %s" % (
+                        response_key, resp_data))
+            resp_data = resp_data[response_key]
+
+        return resp_data
+
+    def _get(self, resource, response_key=None):
+        return self._do_req("GET", resource, response_key=response_key)
+
+    def _post(self, resource, body, response_key=None):
+        return self._do_req(
+            "POST", resource, body=body, response_key=response_key)
+
+    def _put(self, resource, body, response_key=None):
+        return self._do_req(
+            "PUT", resource, body=body, response_key=response_key)
+
+    def _delete(self, resource, body, response_key=None):
+        return self._do_req(
+            "DELETE", resource, body=body, response_key=response_key)
+
+    def get_licence_status(self):
+        """ Gets licence status for appliance. """
+        return self._get("/licence-status", "licence_status")
+
+    def get_licences(self):
+        """ Lists all installed licences. """
+        return self._get("/licences", response_key="licences")
+
+    def add_licence(self, licence_data):
+        """ Sends request to add licence (in .PEM format). """
+        return self._post("/licences", licence_data)
+
+    def add_reservation(self, reservation_type, num_vms):
+        """ Creates a reservation of the given type. """
+        allowed_values = [
+            RESERVATION_TYPE_MIGRATION, RESERVATION_TYPE_REPLICA]
+        if reservation_type not in allowed_values:
+            raise ValueError(
+                "Reservation type must be one of %s" % allowed_values)
+        return self._post(
+            "/reservations", {
+                "type": reservation_type, "count": num_vms},
+            response_key="reservation")
+
+    def add_migrations_reservation(self, num_vms):
+        """ Creates a reservation for the given number of VM Migrations. """
+        return self.add_reservation(RESERVATION_TYPE_MIGRATION, num_vms)
+
+    def add_replicas_reservation(self, num_vms):
+        """ Creates a reservation for the given number of VM Replicas. """
+        return self.add_reservation(RESERVATION_TYPE_REPLICA, num_vms)
+
+    def get_reservations(self):
+        """ Lists all existing reservations. """
+        return self._get("/reservations", response_key="reservations")
+
+    def get_reservation(self, reservation_id):
+        """ Gets a reservation with the given ID.  """
+        return self._get(
+            "/reservations/%s" % reservation_id, response_key="reservation")
+
+    def check_reservation(self, reservation_id):
+        """ Checks the reservation with the given ID.  """
+        return self._post(
+            "/reservations/%s/check" % reservation_id, None,
+            response_key="reservation")
+
+    def delete_reservation(self, reservation_id, raise_on_404=False):
+        """ Deletes a reservation by its ID.
+        Unless `raise_on_404` is set, ignores not found reservations.
+        """
+        resp = self._do_req(
+            "delete", "/reservations/%s" % reservation_id, raw_response=True)
+        if not resp.ok:
+            if resp.status_code == 404:
+                if raise_on_404:
+                    resp.raise_for_status()
+                LOG.warn(
+                    "Got 404 when deleting reservation '%s'", reservation_id)
+            else:
+                resp.raise_for_status()