You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by GitBox <gi...@apache.org> on 2019/12/23 14:22:05 UTC

[GitHub] [libcloud] Eis-D-Z opened a new pull request #1394: Add KubeVirt driver & tests

Eis-D-Z opened a new pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394
 
 
   ## KubeVirt  driver
   
   ### Description
   
   Kubevirt driver with initial support for the k8s/KubeVirt add-on.
   
   Can list, start, stop, destroy kubevirt type vm's from a kubernetes cluster.
   
   Can create a Persistent Volume Claim with the assumption that the Persistent Volume will be created dynamically by Kubernetes, so a relevant storage class that allows for such provisioning must be declared. (You can list storage classes to view them)
   
   Can create a vm with a docker image with embedded disk as image, and as many persistent volume claims as desired. 
     - Network support is limited to 'pod' with 'bridge' or 'masquearde' type interfaces.
    - From the supported disk types of KubeVirt, containerDisk is used for the image, while only  persistentVolumeClaims can be added as disks. Disk types can either be "disk" or "lun" and the supported bus are "virtio", "sata" and "scsi".
   
   
   ### Status
   
   
   - working driver, can be further extended to include more features, needs docstrings
   - Note: Many features of the driver are logically more suited to be in the Kubernetes container driver, please keep this in consideration in the review
   
   
   ### Checklist (tick everything that applies)
   
   - [x] [Code linting](http://libcloud.readthedocs.org/en/latest/development.html#code-style-guide) (required, can be done after the PR checks)
   - [ ] Documentation
   - [x] [Tests](http://libcloud.readthedocs.org/en/latest/testing.html)
   - [ ] [ICLA](http://libcloud.readthedocs.org/en/latest/development.html#contributing-bigger-changes) (required for bigger changes)
   

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214221
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
 
 Review comment:
   All the arguments which are not part of the standard API should be prefixed with ``ex_`` (namespace, memory, cpu, disks, network).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214313
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
+                          (default is 'default')
+        :type namespace: ``str``
+
+        :param image: It must be a Docker image with an embedded disk.
+                      May be a URI like `kubevirt/cirros-registry-disk-demo`,
+                      kubevirt will automatically pull it from
+                      https://hub.docker.com/u/URI.
+                      For more info visit:
+                      https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/disks-and-volumes.html#containerdisk
+        :type image: `str`
+
+        :param kwargs memory: The RAM in MB to be allocated to the VM
+        :type kwargs memory: ``int``
+
+        :param kwargs cpu: The ammount of cpu to be allocated in miliCPUs
+                    ie: 400 will mean 0.4 of a core, 1000 will mean 1 core
+                    and 3000 will mean 3 cores.
+        :type kwargs cpu: ``int``
+
+        :param kwargs disks: A list containing disk dictionaries.
+                             Each dictionaries should have the
+                             following optional keys:
+                             -bus: can be "virtio", "sata", or "scsi"
+                             -device: can be "lun" or "disk"
+                             The following are required keys:
+                             -disk_type: atm only "persistentVolumeClaim"
+                                         is supported
+                             -name: The name of the disk configuration
+                             -claimName: the name of the
+                                         Persistent Volume Claim
+
+                            If you wish a new Persistent Volume Claim can be
+                            created by providing the following:
+                            required:
+                            -size: the desired size (implied in GB)
+                            -storageClassName: the name of the storage class to
+                                               be used for the creation of the
+                                               Persistent Volume Claim.
+                                               Make sure it allows for
+                                               dymamic provisioning.
+                             optional:
+                            -accessMode: default is ReadWriteOnce
+                            -volumeMode: default is `Filesystem`,
+                                         it can also be `Block`
+
+        :type kwarg disks: `list` of `dict`. For each `dict` the types
+                            for its keys are:
+                            -bus: `str`
+                            -device: `str`
+                            -disk_type: `str`
+                            -name: `str`
+                            -claimName: `str`
+                            (for creating a claim:)
+                            -size: `int`
+                            -storageClassName: `str`
+                            -volumeMode: `str`
+                            -accessMode: `str`
+
+        :param kwargs network: Only the pod type is supported, and in the
+                               configuration masquerade or bridge are the
+                               accepted values.
+                               The parameter must be a tupple or list with
+                               (network_type, interface, name)
+        :param type: `iterable` (tupple or list) [network_type, inteface, name]
+                      network_type: `str` | only "pod" is accepted atm
+                      interface: `str` | "masquerade" or "bridge"
+                      name: `str`
+        """
+        # all valid disk types for which support will be added in the future
+        DISK_TYPES = {'containerDisk', 'ephemeral', 'configMap', 'dataVolume',
+                      'cloudInitNoCloud', 'persistentVolumeClaim', 'emptyDisk',
+                      'cloudInitConfigDrive', 'hostDisk'}
+
+        name = kwargs.get("name", "newVM")
+        namespace = kwargs.get('namespace', 'default')
+
+        terminationGracePeriod = kwargs.get('terminationGracePeriod', 0)
+
+        # vm template to be populated
+        vm = {
+            "apiVersion": "kubevirt.io/v1alpha3",
+            "kind": "VirtualMachine",
+            "metadata": {
+                "labels": {
+                    "kubevirt.io/vm": name
+                },
+                "name": name
+            },
+            "spec": {
+                "running": False,
+                "template": {
+                    "metadata": {
+                        "labels": {
+                            "kubevirt.io/vm": name
+                        }
+                    },
+                    "spec": {
+                        "domain": {
+                            "cpu": {},
+                            "devices": {
+                                "disks": [],
+                                "interfaces": [],
+                                "networkInterfaceMultiqueue": False,
+
+                            },
+                            "machine": {
+                                "type": ""
+                            },
+                            "resources": {
+                                "requests": {},
+                            },
+                        },
+                        "networks": [],
+                        "terminationGracePeriodSeconds": terminationGracePeriod,
+                        "volumes": []
+                    }
+                }
+            }
+        }
+
+        if 'memory' in kwargs:
+            if kwargs['memory'] is not None:
+                memory = str(kwargs['memory']) + "M"
+                vm['spec']['template']['spec']['domain']['resources'][
+                    'requests']['memory'] = memory
+        if 'cpu' in kwargs:
+            if kwargs['cpu'] is not None:
+                if kwargs['cpu'] < 10:
+                    cpu = str(kwargs['cpu'])
+                else:
+                    cpu = str(kwargs['cpu']) + "m"
+                vm['spec']['template']['spec']['domain']['cpu']['cores'] = cpu
+
+        disks = kwargs.get('disks', [])
+        i = 0
+        for disk in disks:
+            disk_type = disk['disk_type']
+            bus = disk.get('bus', 'virtio')
+            disk_name = disk.get('name', 'disk{}'.format(i))
+            i += 1
+            device = disk.get('device', 'disk')
+            if disk_type not in DISK_TYPES:
+                raise ValueError("The possible values for this "
+                                 "parameter are: ", DISK_TYPES)
+            # depending on disk_type, in the future,
+            # when more will be supported,
+            # additional elif should be added
+            if disk_type == "containerDisk":
+                try:
+                    image = disk['image']
+                except KeyError as exc:
+                    raise KeyError('A container disk needs a '
+                                   'containerized image')
+
+                volumes_dict = {'containerDisk': {'image': image},
+                                'name': disk_name}
+
+            if disk_type == "persistentVolumeClaim":
+                if 'claimName' in disk:
+                    claimName = disk['claimName']
+                    if claimName not in self.list_persistent_volume_claims(
+                        namespace=namespace
+                    ):
+                        if 'size' not in disk or "storageClassName" not in disk:
+                            msg = ("disk['size'] and "
+                                   "disk['storageClassName'] "
+                                   "are both required to create "
+                                   "a new claim.")
+                            raise KeyError(msg)
+                        size = disk['size']
+                        storage_class = disk['storageClassName']
+                        volume_mode = disk.get('volumeMode', 'Filesystem')
+                        access_mode = disk.get('accessMode', 'ReadWriteOnce')
+                        self.create_volume(size=size, name=claimName,
+                                           storageClassName=storage_class,
+                                           namespace=namespace,
+                                           volumeMode=volume_mode,
+                                           accessMode=access_mode)
+
+                else:
+                    msg = ("You must provide either a claimName of an "
+                           "existing claim or if you want one to be "
+                           "created you must additionally provide size "
+                           "and the storageClassName of the "
+                           "cluster, which allows dynamic provisioning, "
+                           "so a Persistent Volume Claim can be created. "
+                           "In the latter case please provide the desired "
+                           "size as well.")
+                    raise KeyError(msg)
+
+                volumes_dict = {'persistentVolumeClaim': {
+                                'claimName': claimName},
+                                'name': disk_name}
+            disk_dict = {device: {'bus': bus}, 'name': disk_name}
+            vm['spec']['template']['spec']['domain'][
+                'devices']['disks'].append(disk_dict)
+            vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # adding image in a container Disk
+        if 'image' not in kwargs:
+            raise KeyError("An 'image' keyword argument must be specified.")
+        image = kwargs['image']
+        if isinstance(image, NodeImage):
+            image = image.name
+        volumes_dict = {'containerDisk': {'image': image},
+                        'name': 'boot-disk'}
+        disk_dict = {'disk': {'bus': 'virtio'}, 'name': 'boot-disk'}
+        vm['spec']['template']['spec']['domain'][
+            'devices']['disks'].append(disk_dict)
+        vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # network
+        if 'network' in kwargs and kwargs['network']:
+            network = kwargs['network']
+            interface = network[1]
+            network_name = network[2]
+            network_type = network[0]
+        else:
+            interface = 'masquerade'
+            network_name = "netw1"
+            network_type = "pod"
+        network_dict = {network_type: {}, 'name': network_name}
+        interface_dict = {interface: {}, 'name': network_name}
+        vm['spec']['template']['spec'][
+            'networks'].append(network_dict)
+        vm['spec']['template']['spec']['domain']['devices'][
+            'interfaces'].append(interface_dict)
+
+        method = "POST"
+        data = json.dumps(vm)
+        req = KUBEVIRT_URL + "namespaces/" + namespace + "/virtualmachines/"
+        try:
+
+            self.connection.request(req, method=method, data=data)
+
+        except Exception as exc:
+            raise
+        # check if new node is present
+        nodes = self.list_nodes()
+        for node in nodes:
+            if node.name == name:
+                return node
+
+    def list_images(self, location=None):
+        """
+        If location (namespace) is provided only the images
+        in that location will be provided. Otherwise all of them.
+        """
+        nodes = self.list_nodes()
+        if location:
+            namespace = location.name
+            nodes = list(filter(lambda x: x['extra'][
+                                'namespace'] == namespace, nodes))
+
+        images = [node.image for node in nodes]
+        return images
+
+    def list_locations(self):
+        """
+        By locations here it is meant namespaces.
+        """
+        req = ROOT_URL + "namespaces"
+
+        namespaces = []
+        result = self.connection.request(req).object
+        for item in result['items']:
+            name = item['metadata']['name']
+            ID = item['metadata']['uid']
+            namespaces.append(NodeLocation(id=ID, name=name,
+                                           country='',
+                                           driver=self.connection.driver))
+        return namespaces
+
+    def list_sizes(self, location=None):
+
+        namespace = ''
+        if location:
+            namespace = location.name
+        nodes = self.list_nodes()
+        sizes = []
+        for node in nodes:
+            if not namespace:
+                sizes.append(node.size)
+            elif namespace == node.extra['namespace']:
+                sizes.append(node.size)
+
+        return sizes
+
+    def create_volume(self, size, name, storage_class_name,
+                      volume_mode='Filesystem', namespace='default',
+                      access_mode='ReadWriteOnce'):
+        """
+        Method to create a Persistent Volume Claim for storage,
+        thus storage is required in the arguments.
+
+        :param name: The name of the pvc an arbitrary string of lower letters
+        :type name: `str`
+
+        :param size: An int of the ammount of gigabytes desired
+        :type size: `int`
+
+        :param namespace: The namespace where the claim will live
 
 Review comment:
   Please prefix non standard arguments with ``ex_`` (namespace, storageClassName, accessMode, matchlabels).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214562
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
+                          (default is 'default')
+        :type namespace: ``str``
+
+        :param image: It must be a Docker image with an embedded disk.
+                      May be a URI like `kubevirt/cirros-registry-disk-demo`,
+                      kubevirt will automatically pull it from
+                      https://hub.docker.com/u/URI.
+                      For more info visit:
+                      https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/disks-and-volumes.html#containerdisk
+        :type image: `str`
+
+        :param kwargs memory: The RAM in MB to be allocated to the VM
+        :type kwargs memory: ``int``
+
+        :param kwargs cpu: The ammount of cpu to be allocated in miliCPUs
+                    ie: 400 will mean 0.4 of a core, 1000 will mean 1 core
+                    and 3000 will mean 3 cores.
+        :type kwargs cpu: ``int``
+
+        :param kwargs disks: A list containing disk dictionaries.
+                             Each dictionaries should have the
+                             following optional keys:
+                             -bus: can be "virtio", "sata", or "scsi"
+                             -device: can be "lun" or "disk"
+                             The following are required keys:
+                             -disk_type: atm only "persistentVolumeClaim"
+                                         is supported
+                             -name: The name of the disk configuration
+                             -claimName: the name of the
+                                         Persistent Volume Claim
+
+                            If you wish a new Persistent Volume Claim can be
+                            created by providing the following:
+                            required:
+                            -size: the desired size (implied in GB)
+                            -storageClassName: the name of the storage class to
+                                               be used for the creation of the
+                                               Persistent Volume Claim.
+                                               Make sure it allows for
+                                               dymamic provisioning.
+                             optional:
+                            -accessMode: default is ReadWriteOnce
+                            -volumeMode: default is `Filesystem`,
+                                         it can also be `Block`
+
+        :type kwarg disks: `list` of `dict`. For each `dict` the types
+                            for its keys are:
+                            -bus: `str`
+                            -device: `str`
+                            -disk_type: `str`
+                            -name: `str`
+                            -claimName: `str`
+                            (for creating a claim:)
+                            -size: `int`
+                            -storageClassName: `str`
+                            -volumeMode: `str`
+                            -accessMode: `str`
+
+        :param kwargs network: Only the pod type is supported, and in the
+                               configuration masquerade or bridge are the
+                               accepted values.
+                               The parameter must be a tupple or list with
+                               (network_type, interface, name)
+        :param type: `iterable` (tupple or list) [network_type, inteface, name]
+                      network_type: `str` | only "pod" is accepted atm
+                      interface: `str` | "masquerade" or "bridge"
+                      name: `str`
+        """
+        # all valid disk types for which support will be added in the future
+        DISK_TYPES = {'containerDisk', 'ephemeral', 'configMap', 'dataVolume',
+                      'cloudInitNoCloud', 'persistentVolumeClaim', 'emptyDisk',
+                      'cloudInitConfigDrive', 'hostDisk'}
+
+        name = kwargs.get("name", "newVM")
+        namespace = kwargs.get('namespace', 'default')
+
+        terminationGracePeriod = kwargs.get('terminationGracePeriod', 0)
+
+        # vm template to be populated
+        vm = {
+            "apiVersion": "kubevirt.io/v1alpha3",
+            "kind": "VirtualMachine",
+            "metadata": {
+                "labels": {
+                    "kubevirt.io/vm": name
+                },
+                "name": name
+            },
+            "spec": {
+                "running": False,
+                "template": {
+                    "metadata": {
+                        "labels": {
+                            "kubevirt.io/vm": name
+                        }
+                    },
+                    "spec": {
+                        "domain": {
+                            "cpu": {},
+                            "devices": {
+                                "disks": [],
+                                "interfaces": [],
+                                "networkInterfaceMultiqueue": False,
+
+                            },
+                            "machine": {
+                                "type": ""
+                            },
+                            "resources": {
+                                "requests": {},
+                            },
+                        },
+                        "networks": [],
+                        "terminationGracePeriodSeconds": terminationGracePeriod,
+                        "volumes": []
+                    }
+                }
+            }
+        }
+
+        if 'memory' in kwargs:
+            if kwargs['memory'] is not None:
+                memory = str(kwargs['memory']) + "M"
+                vm['spec']['template']['spec']['domain']['resources'][
+                    'requests']['memory'] = memory
+        if 'cpu' in kwargs:
+            if kwargs['cpu'] is not None:
+                if kwargs['cpu'] < 10:
+                    cpu = str(kwargs['cpu'])
+                else:
+                    cpu = str(kwargs['cpu']) + "m"
+                vm['spec']['template']['spec']['domain']['cpu']['cores'] = cpu
+
+        disks = kwargs.get('disks', [])
+        i = 0
+        for disk in disks:
+            disk_type = disk['disk_type']
+            bus = disk.get('bus', 'virtio')
+            disk_name = disk.get('name', 'disk{}'.format(i))
+            i += 1
+            device = disk.get('device', 'disk')
+            if disk_type not in DISK_TYPES:
+                raise ValueError("The possible values for this "
+                                 "parameter are: ", DISK_TYPES)
+            # depending on disk_type, in the future,
+            # when more will be supported,
+            # additional elif should be added
+            if disk_type == "containerDisk":
+                try:
+                    image = disk['image']
+                except KeyError as exc:
+                    raise KeyError('A container disk needs a '
+                                   'containerized image')
+
+                volumes_dict = {'containerDisk': {'image': image},
+                                'name': disk_name}
+
+            if disk_type == "persistentVolumeClaim":
+                if 'claimName' in disk:
+                    claimName = disk['claimName']
+                    if claimName not in self.list_persistent_volume_claims(
+                        namespace=namespace
+                    ):
+                        if 'size' not in disk or "storageClassName" not in disk:
+                            msg = ("disk['size'] and "
+                                   "disk['storageClassName'] "
+                                   "are both required to create "
+                                   "a new claim.")
+                            raise KeyError(msg)
+                        size = disk['size']
+                        storage_class = disk['storageClassName']
+                        volume_mode = disk.get('volumeMode', 'Filesystem')
+                        access_mode = disk.get('accessMode', 'ReadWriteOnce')
+                        self.create_volume(size=size, name=claimName,
+                                           storageClassName=storage_class,
+                                           namespace=namespace,
+                                           volumeMode=volume_mode,
+                                           accessMode=access_mode)
+
+                else:
+                    msg = ("You must provide either a claimName of an "
+                           "existing claim or if you want one to be "
+                           "created you must additionally provide size "
+                           "and the storageClassName of the "
+                           "cluster, which allows dynamic provisioning, "
+                           "so a Persistent Volume Claim can be created. "
+                           "In the latter case please provide the desired "
+                           "size as well.")
+                    raise KeyError(msg)
+
+                volumes_dict = {'persistentVolumeClaim': {
+                                'claimName': claimName},
+                                'name': disk_name}
+            disk_dict = {device: {'bus': bus}, 'name': disk_name}
+            vm['spec']['template']['spec']['domain'][
+                'devices']['disks'].append(disk_dict)
+            vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # adding image in a container Disk
+        if 'image' not in kwargs:
+            raise KeyError("An 'image' keyword argument must be specified.")
+        image = kwargs['image']
+        if isinstance(image, NodeImage):
+            image = image.name
+        volumes_dict = {'containerDisk': {'image': image},
+                        'name': 'boot-disk'}
+        disk_dict = {'disk': {'bus': 'virtio'}, 'name': 'boot-disk'}
+        vm['spec']['template']['spec']['domain'][
+            'devices']['disks'].append(disk_dict)
+        vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # network
+        if 'network' in kwargs and kwargs['network']:
+            network = kwargs['network']
+            interface = network[1]
+            network_name = network[2]
+            network_type = network[0]
+        else:
+            interface = 'masquerade'
+            network_name = "netw1"
+            network_type = "pod"
+        network_dict = {network_type: {}, 'name': network_name}
+        interface_dict = {interface: {}, 'name': network_name}
+        vm['spec']['template']['spec'][
+            'networks'].append(network_dict)
+        vm['spec']['template']['spec']['domain']['devices'][
+            'interfaces'].append(interface_dict)
+
+        method = "POST"
+        data = json.dumps(vm)
+        req = KUBEVIRT_URL + "namespaces/" + namespace + "/virtualmachines/"
+        try:
+
+            self.connection.request(req, method=method, data=data)
+
+        except Exception as exc:
+            raise
+        # check if new node is present
+        nodes = self.list_nodes()
+        for node in nodes:
+            if node.name == name:
+                return node
+
+    def list_images(self, location=None):
+        """
+        If location (namespace) is provided only the images
+        in that location will be provided. Otherwise all of them.
+        """
+        nodes = self.list_nodes()
+        if location:
+            namespace = location.name
+            nodes = list(filter(lambda x: x['extra'][
+                                'namespace'] == namespace, nodes))
+
+        images = [node.image for node in nodes]
+        return images
+
+    def list_locations(self):
+        """
+        By locations here it is meant namespaces.
+        """
+        req = ROOT_URL + "namespaces"
+
+        namespaces = []
+        result = self.connection.request(req).object
+        for item in result['items']:
+            name = item['metadata']['name']
+            ID = item['metadata']['uid']
+            namespaces.append(NodeLocation(id=ID, name=name,
+                                           country='',
+                                           driver=self.connection.driver))
+        return namespaces
+
+    def list_sizes(self, location=None):
+
+        namespace = ''
+        if location:
+            namespace = location.name
+        nodes = self.list_nodes()
+        sizes = []
+        for node in nodes:
+            if not namespace:
+                sizes.append(node.size)
+            elif namespace == node.extra['namespace']:
+                sizes.append(node.size)
+
+        return sizes
+
+    def create_volume(self, size, name, storage_class_name,
+                      volume_mode='Filesystem', namespace='default',
+                      access_mode='ReadWriteOnce'):
+        """
+        Method to create a Persistent Volume Claim for storage,
+        thus storage is required in the arguments.
+
+        :param name: The name of the pvc an arbitrary string of lower letters
+        :type name: `str`
+
+        :param size: An int of the ammount of gigabytes desired
+        :type size: `int`
+
+        :param namespace: The namespace where the claim will live
+        :type namespace: `str`
+
+        :param storageClassName: If you want the pvc to be bound to
+                                 a particular class of PVs specified here.
+        :type storageClassName: `str`
+
+        :param accessMode: The desired access mode, ie "ReadOnlyMany"
+        :type accessMode: `str`
+
+        :param matchLabels: A dictionary with the labels, ie:
+                            {'release': 'stable,}
+        :type matchLabels: `dict` with keys `str` and values `str`
+        """
+        pvc = {
+            'apiVersion': 'v1',
+            'kind': 'PersistentVolumeClaim',
+            'metadata': {
+                'name': name
+            },
+            'spec': {
+                'accessModes': [],
+                'volumeMode': volume_mode,
+                'resources': {
+                    'requests': {
+                        'storage': ''
+                    }
+                },
+                'selector': {}
+            }
+        }
+
+        pvc['spec']['accessModes'].append(access_mode)
+
+        if storage_class_name is not None:
+            pvc['spec']['storageClassName'] = storage_class_name
+        else:
+            raise ValueError("The storage class name must be provided of a"
+                             "storage class which allows for dynamic "
+                             "provisioning")
+        pvc['spec']['resources']['requests']['storage'] = str(size) + 'Gi'
+
+        method = "POST"
+        req = ROOT_URL + "namespaces/" + namespace + "/persistentvolumeclaims"
+        data = json.dumps(pvc)
+        try:
+            result = self.connection.request(req, method=method, data=data)
+        except Exception as exc:
+            raise
+        if result.object['status']['phase'] != "Bound":
+            for _ in range(3):
+
+                req = ROOT_URL + "namespaces/" + namespace + \
+                    "/persistentvolumeclaims/" + name
+                try:
+                    result = self.connection.request(req).object
+                except Exception as exc:
+                    raise
+                if result['status']['phase'] == "Bound":
+                    break
+                time.sleep(3)
+
+        # check that the pv was created and bound
+        volumes = self.list_volumes()
+        for volume in volumes:
+            if volume.extra['pvc']['name'] == name:
+                return volume
+
+    def destroy_volume(self, volume):
+        # first delete the pvc
+        if volume.extra['isBound']:
+            pvc = volume.extra['pvc']['name']
+            namespace = volume.extra['pvc']['namespace']
+            method = 'DELETE'
+            req = ROOT_URL + "namespaces/" + namespace + \
+                "/persistentvolumeclaims/" + pvc
+            try:
+                result = self.connection.request(req, method=method)
+
+            except Exception as exc:
+                raise
+
+        pv = volume.name
+        req = ROOT_URL + "persistentvolumes/" + pv
+
+        try:
+            result = self.connection.request(req, method=method)
+            return result.status
+        except Exception as exc:
+            raise
+
+    def attach_volume(self, volume, ex_node, **kwargs):
+        """
+        kwargs: bus, name , device (disk or lun)
+        """
+        # volume must be bound to a claim
+        if not volume.extra['isBound']:
+            raise ValueError("""
+            This volume is not bound to a claim,
+            pick a volume that is or create a claim""")
+        claimName = volume.extra['pvc']['name']
+        name = kwargs.get('name', claimName)
+        bus = kwargs.get('bus', 'virtio')
+        device = kwargs.get('device', 'disk')
+        namespace = volume.extra['pvc']['namespace']
+        # check if vm is stopped
+        self.ex_stop_node(ex_node)
+        # check if it is the same namespace
+        if ex_node.extra['namespace'] != namespace:
+            msg = "The PVC and the VM must be in the same namespace"
+            raise ValueError(msg)
+        vm = ex_node.name
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + '/virtualmachines/'\
+            + vm
+        disk_dict = {device: {'bus': bus}, 'name': name}
+        volumes_dict = {'persistentVolumeClaim': {'claimName': claimName},
+                        'name': name}
+        # Get all the volumes of the vm
+        try:
+            result = self.connection.request(req).object
+        except Exception as exc:
+            raise
+        disks = result['spec']['template']['spec']['domain'][
+            'devices']['disks']
+        volumes = result['spec']['template']['spec']['volumes']
+        disks.append(disk_dict)
+        volumes.append(volumes_dict)
+        # now patch the new volumes and disks lists into the resource
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {'spec': {
+            'template': {
+                'spec': {
+                    'volumes': volumes,
+                    'domain': {
+                        'devices':
+                        {'disks': disks}
+                    }
+                }
+            }
+        }
+        }
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+            if 'pvcs' in ex_node.extra:
+                ex_node.extra['pvcs'].append(claimName)
+            else:
+                ex_node.extra['pvcs'] = [claimName]
+            return result in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    def detach_volume(self, volume, ex_node):
+        """
+        Detaches a volume from a node but the node must be given since a PVC
+        can have more than one VMI's pointing to it
+        """
+        # vmi must be stopped
+        self.ex_stop_node(ex_node)
+
+        claimName = volume.extra['pvc']['name']
+        name = ex_node.name
+        namespace = ex_node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + '/virtualmachines/'\
+            + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        # Get all the volumes of the vm
+
+        try:
+            result = self.connection.request(req).object
+        except Exception as exc:
+            raise
+        disks = result['spec']['template']['spec']['domain'][
+            'devices']['disks']
+        volumes = result['spec']['template']['spec']['volumes']
+        to_delete = None
+        for volume in volumes:
+            if 'persistentVolumeClaim' in volume:
+                if volume['persistentVolumeClaim']['claimName'] == claimName:
+                    to_delete = volume['name']
+                    volumes.remove(volume)
+                    break
+        if not to_delete:
+            msg = "The given volume is not attached to the given VM"
+            raise ValueError(msg)
+
+        for disk in disks:
+            if disk['name'] == to_delete:
+                disks.remove(disk)
+                break
+        # now patch the new volumes and disks lists into the resource
+        data = {'spec': {
+            'template': {
+                'spec': {
+                    'volumes': volumes,
+                    'domain': {
+                        'devices':
+                        {'disks': disks}
+                    }
+                }
+            }
+        }
+        }
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+            ex_node.extra['pvcs'].remove(claimName)
+            return result in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    def list_persistent_volume_claims(self, namespace="default"):
 
 Review comment:
   Please prefix this method with ``ex_`` since is not part of the standard API.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213510
 
 

 ##########
 File path: libcloud/http.py
 ##########
 @@ -194,20 +194,22 @@ def __init__(self, host, port, secure=None, **kwargs):
         http_proxy_url_env = os.environ.get(HTTP_PROXY_ENV_VARIABLE_NAME,
                                             https_proxy_url_env)
 
-        # Connection argument rgument has precedence over environment variables
+        # Connection argument has precedence over environment variables
         proxy_url = kwargs.pop('proxy_url', http_proxy_url_env)
 
         self._setup_verify()
         self._setup_ca_cert()
 
         LibcloudBaseConnection.__init__(self)
 
+        self.session.timeout = kwargs.pop('timeout', 60)
 
 Review comment:
   EDIT: Never mind, I see you just moved this statement a couple of lines above, please ignore my comment.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214570
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
+                          (default is 'default')
+        :type namespace: ``str``
+
+        :param image: It must be a Docker image with an embedded disk.
+                      May be a URI like `kubevirt/cirros-registry-disk-demo`,
+                      kubevirt will automatically pull it from
+                      https://hub.docker.com/u/URI.
+                      For more info visit:
+                      https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/disks-and-volumes.html#containerdisk
+        :type image: `str`
+
+        :param kwargs memory: The RAM in MB to be allocated to the VM
+        :type kwargs memory: ``int``
+
+        :param kwargs cpu: The ammount of cpu to be allocated in miliCPUs
+                    ie: 400 will mean 0.4 of a core, 1000 will mean 1 core
+                    and 3000 will mean 3 cores.
+        :type kwargs cpu: ``int``
+
+        :param kwargs disks: A list containing disk dictionaries.
+                             Each dictionaries should have the
+                             following optional keys:
+                             -bus: can be "virtio", "sata", or "scsi"
+                             -device: can be "lun" or "disk"
+                             The following are required keys:
+                             -disk_type: atm only "persistentVolumeClaim"
+                                         is supported
+                             -name: The name of the disk configuration
+                             -claimName: the name of the
+                                         Persistent Volume Claim
+
+                            If you wish a new Persistent Volume Claim can be
+                            created by providing the following:
+                            required:
+                            -size: the desired size (implied in GB)
+                            -storageClassName: the name of the storage class to
+                                               be used for the creation of the
+                                               Persistent Volume Claim.
+                                               Make sure it allows for
+                                               dymamic provisioning.
+                             optional:
+                            -accessMode: default is ReadWriteOnce
+                            -volumeMode: default is `Filesystem`,
+                                         it can also be `Block`
+
+        :type kwarg disks: `list` of `dict`. For each `dict` the types
+                            for its keys are:
+                            -bus: `str`
+                            -device: `str`
+                            -disk_type: `str`
+                            -name: `str`
+                            -claimName: `str`
+                            (for creating a claim:)
+                            -size: `int`
+                            -storageClassName: `str`
+                            -volumeMode: `str`
+                            -accessMode: `str`
+
+        :param kwargs network: Only the pod type is supported, and in the
+                               configuration masquerade or bridge are the
+                               accepted values.
+                               The parameter must be a tupple or list with
+                               (network_type, interface, name)
+        :param type: `iterable` (tupple or list) [network_type, inteface, name]
+                      network_type: `str` | only "pod" is accepted atm
+                      interface: `str` | "masquerade" or "bridge"
+                      name: `str`
+        """
+        # all valid disk types for which support will be added in the future
+        DISK_TYPES = {'containerDisk', 'ephemeral', 'configMap', 'dataVolume',
+                      'cloudInitNoCloud', 'persistentVolumeClaim', 'emptyDisk',
+                      'cloudInitConfigDrive', 'hostDisk'}
+
+        name = kwargs.get("name", "newVM")
+        namespace = kwargs.get('namespace', 'default')
+
+        terminationGracePeriod = kwargs.get('terminationGracePeriod', 0)
+
+        # vm template to be populated
+        vm = {
+            "apiVersion": "kubevirt.io/v1alpha3",
+            "kind": "VirtualMachine",
+            "metadata": {
+                "labels": {
+                    "kubevirt.io/vm": name
+                },
+                "name": name
+            },
+            "spec": {
+                "running": False,
+                "template": {
+                    "metadata": {
+                        "labels": {
+                            "kubevirt.io/vm": name
+                        }
+                    },
+                    "spec": {
+                        "domain": {
+                            "cpu": {},
+                            "devices": {
+                                "disks": [],
+                                "interfaces": [],
+                                "networkInterfaceMultiqueue": False,
+
+                            },
+                            "machine": {
+                                "type": ""
+                            },
+                            "resources": {
+                                "requests": {},
+                            },
+                        },
+                        "networks": [],
+                        "terminationGracePeriodSeconds": terminationGracePeriod,
+                        "volumes": []
+                    }
+                }
+            }
+        }
+
+        if 'memory' in kwargs:
+            if kwargs['memory'] is not None:
+                memory = str(kwargs['memory']) + "M"
+                vm['spec']['template']['spec']['domain']['resources'][
+                    'requests']['memory'] = memory
+        if 'cpu' in kwargs:
+            if kwargs['cpu'] is not None:
+                if kwargs['cpu'] < 10:
+                    cpu = str(kwargs['cpu'])
+                else:
+                    cpu = str(kwargs['cpu']) + "m"
+                vm['spec']['template']['spec']['domain']['cpu']['cores'] = cpu
+
+        disks = kwargs.get('disks', [])
+        i = 0
+        for disk in disks:
+            disk_type = disk['disk_type']
+            bus = disk.get('bus', 'virtio')
+            disk_name = disk.get('name', 'disk{}'.format(i))
+            i += 1
+            device = disk.get('device', 'disk')
+            if disk_type not in DISK_TYPES:
+                raise ValueError("The possible values for this "
+                                 "parameter are: ", DISK_TYPES)
+            # depending on disk_type, in the future,
+            # when more will be supported,
+            # additional elif should be added
+            if disk_type == "containerDisk":
+                try:
+                    image = disk['image']
+                except KeyError as exc:
+                    raise KeyError('A container disk needs a '
+                                   'containerized image')
+
+                volumes_dict = {'containerDisk': {'image': image},
+                                'name': disk_name}
+
+            if disk_type == "persistentVolumeClaim":
+                if 'claimName' in disk:
+                    claimName = disk['claimName']
+                    if claimName not in self.list_persistent_volume_claims(
+                        namespace=namespace
+                    ):
+                        if 'size' not in disk or "storageClassName" not in disk:
+                            msg = ("disk['size'] and "
+                                   "disk['storageClassName'] "
+                                   "are both required to create "
+                                   "a new claim.")
+                            raise KeyError(msg)
+                        size = disk['size']
+                        storage_class = disk['storageClassName']
+                        volume_mode = disk.get('volumeMode', 'Filesystem')
+                        access_mode = disk.get('accessMode', 'ReadWriteOnce')
+                        self.create_volume(size=size, name=claimName,
+                                           storageClassName=storage_class,
+                                           namespace=namespace,
+                                           volumeMode=volume_mode,
+                                           accessMode=access_mode)
+
+                else:
+                    msg = ("You must provide either a claimName of an "
+                           "existing claim or if you want one to be "
+                           "created you must additionally provide size "
+                           "and the storageClassName of the "
+                           "cluster, which allows dynamic provisioning, "
+                           "so a Persistent Volume Claim can be created. "
+                           "In the latter case please provide the desired "
+                           "size as well.")
+                    raise KeyError(msg)
+
+                volumes_dict = {'persistentVolumeClaim': {
+                                'claimName': claimName},
+                                'name': disk_name}
+            disk_dict = {device: {'bus': bus}, 'name': disk_name}
+            vm['spec']['template']['spec']['domain'][
+                'devices']['disks'].append(disk_dict)
+            vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # adding image in a container Disk
+        if 'image' not in kwargs:
+            raise KeyError("An 'image' keyword argument must be specified.")
+        image = kwargs['image']
+        if isinstance(image, NodeImage):
+            image = image.name
+        volumes_dict = {'containerDisk': {'image': image},
+                        'name': 'boot-disk'}
+        disk_dict = {'disk': {'bus': 'virtio'}, 'name': 'boot-disk'}
+        vm['spec']['template']['spec']['domain'][
+            'devices']['disks'].append(disk_dict)
+        vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # network
+        if 'network' in kwargs and kwargs['network']:
+            network = kwargs['network']
+            interface = network[1]
+            network_name = network[2]
+            network_type = network[0]
+        else:
+            interface = 'masquerade'
+            network_name = "netw1"
+            network_type = "pod"
+        network_dict = {network_type: {}, 'name': network_name}
+        interface_dict = {interface: {}, 'name': network_name}
+        vm['spec']['template']['spec'][
+            'networks'].append(network_dict)
+        vm['spec']['template']['spec']['domain']['devices'][
+            'interfaces'].append(interface_dict)
+
+        method = "POST"
+        data = json.dumps(vm)
+        req = KUBEVIRT_URL + "namespaces/" + namespace + "/virtualmachines/"
+        try:
+
+            self.connection.request(req, method=method, data=data)
+
+        except Exception as exc:
+            raise
+        # check if new node is present
+        nodes = self.list_nodes()
+        for node in nodes:
+            if node.name == name:
+                return node
+
+    def list_images(self, location=None):
+        """
+        If location (namespace) is provided only the images
+        in that location will be provided. Otherwise all of them.
+        """
+        nodes = self.list_nodes()
+        if location:
+            namespace = location.name
+            nodes = list(filter(lambda x: x['extra'][
+                                'namespace'] == namespace, nodes))
+
+        images = [node.image for node in nodes]
+        return images
+
+    def list_locations(self):
+        """
+        By locations here it is meant namespaces.
+        """
+        req = ROOT_URL + "namespaces"
+
+        namespaces = []
+        result = self.connection.request(req).object
+        for item in result['items']:
+            name = item['metadata']['name']
+            ID = item['metadata']['uid']
+            namespaces.append(NodeLocation(id=ID, name=name,
+                                           country='',
+                                           driver=self.connection.driver))
+        return namespaces
+
+    def list_sizes(self, location=None):
+
+        namespace = ''
+        if location:
+            namespace = location.name
+        nodes = self.list_nodes()
+        sizes = []
+        for node in nodes:
+            if not namespace:
+                sizes.append(node.size)
+            elif namespace == node.extra['namespace']:
+                sizes.append(node.size)
+
+        return sizes
+
+    def create_volume(self, size, name, storage_class_name,
+                      volume_mode='Filesystem', namespace='default',
+                      access_mode='ReadWriteOnce'):
+        """
+        Method to create a Persistent Volume Claim for storage,
+        thus storage is required in the arguments.
+
+        :param name: The name of the pvc an arbitrary string of lower letters
+        :type name: `str`
+
+        :param size: An int of the ammount of gigabytes desired
+        :type size: `int`
+
+        :param namespace: The namespace where the claim will live
+        :type namespace: `str`
+
+        :param storageClassName: If you want the pvc to be bound to
+                                 a particular class of PVs specified here.
+        :type storageClassName: `str`
+
+        :param accessMode: The desired access mode, ie "ReadOnlyMany"
+        :type accessMode: `str`
+
+        :param matchLabels: A dictionary with the labels, ie:
+                            {'release': 'stable,}
+        :type matchLabels: `dict` with keys `str` and values `str`
+        """
+        pvc = {
+            'apiVersion': 'v1',
+            'kind': 'PersistentVolumeClaim',
+            'metadata': {
+                'name': name
+            },
+            'spec': {
+                'accessModes': [],
+                'volumeMode': volume_mode,
+                'resources': {
+                    'requests': {
+                        'storage': ''
+                    }
+                },
+                'selector': {}
+            }
+        }
+
+        pvc['spec']['accessModes'].append(access_mode)
+
+        if storage_class_name is not None:
+            pvc['spec']['storageClassName'] = storage_class_name
+        else:
+            raise ValueError("The storage class name must be provided of a"
+                             "storage class which allows for dynamic "
+                             "provisioning")
+        pvc['spec']['resources']['requests']['storage'] = str(size) + 'Gi'
+
+        method = "POST"
+        req = ROOT_URL + "namespaces/" + namespace + "/persistentvolumeclaims"
+        data = json.dumps(pvc)
+        try:
+            result = self.connection.request(req, method=method, data=data)
+        except Exception as exc:
+            raise
+        if result.object['status']['phase'] != "Bound":
+            for _ in range(3):
+
+                req = ROOT_URL + "namespaces/" + namespace + \
+                    "/persistentvolumeclaims/" + name
+                try:
+                    result = self.connection.request(req).object
+                except Exception as exc:
+                    raise
+                if result['status']['phase'] == "Bound":
+                    break
+                time.sleep(3)
+
+        # check that the pv was created and bound
+        volumes = self.list_volumes()
+        for volume in volumes:
+            if volume.extra['pvc']['name'] == name:
+                return volume
+
+    def destroy_volume(self, volume):
+        # first delete the pvc
+        if volume.extra['isBound']:
+            pvc = volume.extra['pvc']['name']
+            namespace = volume.extra['pvc']['namespace']
+            method = 'DELETE'
+            req = ROOT_URL + "namespaces/" + namespace + \
+                "/persistentvolumeclaims/" + pvc
+            try:
+                result = self.connection.request(req, method=method)
+
+            except Exception as exc:
+                raise
+
+        pv = volume.name
+        req = ROOT_URL + "persistentvolumes/" + pv
+
+        try:
+            result = self.connection.request(req, method=method)
+            return result.status
+        except Exception as exc:
+            raise
+
+    def attach_volume(self, volume, ex_node, **kwargs):
+        """
+        kwargs: bus, name , device (disk or lun)
+        """
+        # volume must be bound to a claim
+        if not volume.extra['isBound']:
+            raise ValueError("""
+            This volume is not bound to a claim,
+            pick a volume that is or create a claim""")
+        claimName = volume.extra['pvc']['name']
+        name = kwargs.get('name', claimName)
+        bus = kwargs.get('bus', 'virtio')
+        device = kwargs.get('device', 'disk')
+        namespace = volume.extra['pvc']['namespace']
+        # check if vm is stopped
+        self.ex_stop_node(ex_node)
+        # check if it is the same namespace
+        if ex_node.extra['namespace'] != namespace:
+            msg = "The PVC and the VM must be in the same namespace"
+            raise ValueError(msg)
+        vm = ex_node.name
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + '/virtualmachines/'\
+            + vm
+        disk_dict = {device: {'bus': bus}, 'name': name}
+        volumes_dict = {'persistentVolumeClaim': {'claimName': claimName},
+                        'name': name}
+        # Get all the volumes of the vm
+        try:
+            result = self.connection.request(req).object
+        except Exception as exc:
+            raise
+        disks = result['spec']['template']['spec']['domain'][
+            'devices']['disks']
+        volumes = result['spec']['template']['spec']['volumes']
+        disks.append(disk_dict)
+        volumes.append(volumes_dict)
+        # now patch the new volumes and disks lists into the resource
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {'spec': {
+            'template': {
+                'spec': {
+                    'volumes': volumes,
+                    'domain': {
+                        'devices':
+                        {'disks': disks}
+                    }
+                }
+            }
+        }
+        }
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+            if 'pvcs' in ex_node.extra:
+                ex_node.extra['pvcs'].append(claimName)
+            else:
+                ex_node.extra['pvcs'] = [claimName]
+            return result in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    def detach_volume(self, volume, ex_node):
+        """
+        Detaches a volume from a node but the node must be given since a PVC
+        can have more than one VMI's pointing to it
+        """
+        # vmi must be stopped
+        self.ex_stop_node(ex_node)
+
+        claimName = volume.extra['pvc']['name']
+        name = ex_node.name
+        namespace = ex_node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + '/virtualmachines/'\
+            + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        # Get all the volumes of the vm
+
+        try:
+            result = self.connection.request(req).object
+        except Exception as exc:
+            raise
+        disks = result['spec']['template']['spec']['domain'][
+            'devices']['disks']
+        volumes = result['spec']['template']['spec']['volumes']
+        to_delete = None
+        for volume in volumes:
+            if 'persistentVolumeClaim' in volume:
+                if volume['persistentVolumeClaim']['claimName'] == claimName:
+                    to_delete = volume['name']
+                    volumes.remove(volume)
+                    break
+        if not to_delete:
+            msg = "The given volume is not attached to the given VM"
+            raise ValueError(msg)
+
+        for disk in disks:
+            if disk['name'] == to_delete:
+                disks.remove(disk)
+                break
+        # now patch the new volumes and disks lists into the resource
+        data = {'spec': {
+            'template': {
+                'spec': {
+                    'volumes': volumes,
+                    'domain': {
+                        'devices':
+                        {'disks': disks}
+                    }
+                }
+            }
+        }
+        }
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+            ex_node.extra['pvcs'].remove(claimName)
+            return result in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    def list_persistent_volume_claims(self, namespace="default"):
+
+        pvc_req = ROOT_URL + "namespaces/" + namespace + \
+            "/persistentvolumeclaims"
+        try:
+            result = self.connection.request(pvc_req).object
+        except Exception as exc:
+            raise
+        pvcs = [item['metadata']['name'] for item in result['items']]
+        return pvcs
+
+    def list_storage_classes(self):
 
 Review comment:
   Please prefix this method with ``ex_`` since is not part of the standard API.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214161
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
 
 Review comment:
   Please explicitly declare all the supported arguments (we just did ``**kwargs`` misuse cleanup in #1389).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214007
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
 
 Review comment:
   This method is not part of the standard API so please prefix the method name with ``ex_``.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] codecov-io commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
codecov-io commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-577844680
 
 
   # [Codecov](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=h1) Report
   > Merging [#1394](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=desc) into [trunk](https://codecov.io/gh/apache/libcloud/commit/a63b90eeddff9e85ae191b338f09b86e2f2a1713?src=pr&el=desc) will **decrease** coverage by `0.44%`.
   > The diff coverage is `34.04%`.
   
   [![Impacted file tree graph](https://codecov.io/gh/apache/libcloud/pull/1394/graphs/tree.svg?width=650&token=PYoduksh69&height=150&src=pr)](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=tree)
   
   ```diff
   @@            Coverage Diff             @@
   ##            trunk    #1394      +/-   ##
   ==========================================
   - Coverage   86.46%   86.02%   -0.45%     
   ==========================================
     Files         366      368       +2     
     Lines       76799    77449     +650     
     Branches     7529     7658     +129     
   ==========================================
   + Hits        66404    66622     +218     
   - Misses       7527     7922     +395     
   - Partials     2868     2905      +37
   ```
   
   
   | [Impacted Files](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=tree) | Coverage Δ | |
   |---|---|---|
   | [libcloud/compute/providers.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvY29tcHV0ZS9wcm92aWRlcnMucHk=) | `87.5% <ø> (ø)` | :arrow_up: |
   | [libcloud/compute/types.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvY29tcHV0ZS90eXBlcy5weQ==) | `98.88% <100%> (ø)` | :arrow_up: |
   | [libcloud/http.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvaHR0cC5weQ==) | `91.17% <100%> (ø)` | :arrow_up: |
   | [libcloud/compute/drivers/kubevirt.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvY29tcHV0ZS9kcml2ZXJzL2t1YmV2aXJ0LnB5) | `26.41% <26.41%> (ø)` | |
   | [libcloud/test/compute/test\_kubevirt.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvdGVzdC9jb21wdXRlL3Rlc3Rfa3ViZXZpcnQucHk=) | `66.66% <66.66%> (ø)` | |
   | [libcloud/container/drivers/kubernetes.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvY29udGFpbmVyL2RyaXZlcnMva3ViZXJuZXRlcy5weQ==) | `73.94% <66.66%> (-0.22%)` | :arrow_down: |
   | [libcloud/common/gandi\_live.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvY29tbW9uL2dhbmRpX2xpdmUucHk=) | `95.23% <0%> (-4.77%)` | :arrow_down: |
   | [libcloud/test/dns/test\_gandi\_live.py](https://codecov.io/gh/apache/libcloud/pull/1394/diff?src=pr&el=tree#diff-bGliY2xvdWQvdGVzdC9kbnMvdGVzdF9nYW5kaV9saXZlLnB5) | `100% <0%> (ø)` | :arrow_up: |
   
   ------
   
   [Continue to review full report at Codecov](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=continue).
   > **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
   > `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
   > Powered by [Codecov](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=footer). Last update [a63b90e...b10ab0e](https://codecov.io/gh/apache/libcloud/pull/1394?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).
   

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-573997018
 
 
   @Eis-D-Z LXD driver has been merged, can you please also sync this branch with latest trunk and address the PR comments when you get a chance so this PR can be merged as well.
   
   Thanks.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213946
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
 
 Review comment:
   ``namespace`` argument is unique to this driver and not part of the standard API so please prefix it with ``ex_``.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami merged pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami merged pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394
 
 
   

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Eis-D-Z commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Eis-D-Z commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-575284235
 
 
   Hello, Kami.
   Thank you for the comments. I have added some more functionality and rectified many of the things you pointed out. In the next days I will push the final version.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-568511686
 
 
   Thanks for the contribution.
   
   Since this is a larger contribution, can you please also work on signing an ICLA (https://libcloud.readthedocs.io/en/latest/development.html#contributing-bigger-changes)?

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-568792309
 
 
   Thanks for the contribution and nice work :+1: 
   
   I've added some comments, most of them are related to making sure the driver complies with the "standard" Libcloud compute API (aka making sure non standard arguments and method names are prefixed with ``ex_``).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213604
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
 
 Review comment:
   Please add a missing Apache 2.0 license header to the new Python files.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213772
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
 
 Review comment:
   This was changed recently (https://github.com/apache/libcloud/pull/1375) so this custom node class shouldn't be needed anymore.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214524
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
+                          (default is 'default')
+        :type namespace: ``str``
+
+        :param image: It must be a Docker image with an embedded disk.
+                      May be a URI like `kubevirt/cirros-registry-disk-demo`,
+                      kubevirt will automatically pull it from
+                      https://hub.docker.com/u/URI.
+                      For more info visit:
+                      https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/disks-and-volumes.html#containerdisk
+        :type image: `str`
+
+        :param kwargs memory: The RAM in MB to be allocated to the VM
+        :type kwargs memory: ``int``
+
+        :param kwargs cpu: The ammount of cpu to be allocated in miliCPUs
+                    ie: 400 will mean 0.4 of a core, 1000 will mean 1 core
+                    and 3000 will mean 3 cores.
+        :type kwargs cpu: ``int``
+
+        :param kwargs disks: A list containing disk dictionaries.
+                             Each dictionaries should have the
+                             following optional keys:
+                             -bus: can be "virtio", "sata", or "scsi"
+                             -device: can be "lun" or "disk"
+                             The following are required keys:
+                             -disk_type: atm only "persistentVolumeClaim"
+                                         is supported
+                             -name: The name of the disk configuration
+                             -claimName: the name of the
+                                         Persistent Volume Claim
+
+                            If you wish a new Persistent Volume Claim can be
+                            created by providing the following:
+                            required:
+                            -size: the desired size (implied in GB)
+                            -storageClassName: the name of the storage class to
+                                               be used for the creation of the
+                                               Persistent Volume Claim.
+                                               Make sure it allows for
+                                               dymamic provisioning.
+                             optional:
+                            -accessMode: default is ReadWriteOnce
+                            -volumeMode: default is `Filesystem`,
+                                         it can also be `Block`
+
+        :type kwarg disks: `list` of `dict`. For each `dict` the types
+                            for its keys are:
+                            -bus: `str`
+                            -device: `str`
+                            -disk_type: `str`
+                            -name: `str`
+                            -claimName: `str`
+                            (for creating a claim:)
+                            -size: `int`
+                            -storageClassName: `str`
+                            -volumeMode: `str`
+                            -accessMode: `str`
+
+        :param kwargs network: Only the pod type is supported, and in the
+                               configuration masquerade or bridge are the
+                               accepted values.
+                               The parameter must be a tupple or list with
+                               (network_type, interface, name)
+        :param type: `iterable` (tupple or list) [network_type, inteface, name]
+                      network_type: `str` | only "pod" is accepted atm
+                      interface: `str` | "masquerade" or "bridge"
+                      name: `str`
+        """
+        # all valid disk types for which support will be added in the future
+        DISK_TYPES = {'containerDisk', 'ephemeral', 'configMap', 'dataVolume',
+                      'cloudInitNoCloud', 'persistentVolumeClaim', 'emptyDisk',
+                      'cloudInitConfigDrive', 'hostDisk'}
+
+        name = kwargs.get("name", "newVM")
+        namespace = kwargs.get('namespace', 'default')
+
+        terminationGracePeriod = kwargs.get('terminationGracePeriod', 0)
+
+        # vm template to be populated
+        vm = {
+            "apiVersion": "kubevirt.io/v1alpha3",
+            "kind": "VirtualMachine",
+            "metadata": {
+                "labels": {
+                    "kubevirt.io/vm": name
+                },
+                "name": name
+            },
+            "spec": {
+                "running": False,
+                "template": {
+                    "metadata": {
+                        "labels": {
+                            "kubevirt.io/vm": name
+                        }
+                    },
+                    "spec": {
+                        "domain": {
+                            "cpu": {},
+                            "devices": {
+                                "disks": [],
+                                "interfaces": [],
+                                "networkInterfaceMultiqueue": False,
+
+                            },
+                            "machine": {
+                                "type": ""
+                            },
+                            "resources": {
+                                "requests": {},
+                            },
+                        },
+                        "networks": [],
+                        "terminationGracePeriodSeconds": terminationGracePeriod,
+                        "volumes": []
+                    }
+                }
+            }
+        }
+
+        if 'memory' in kwargs:
+            if kwargs['memory'] is not None:
+                memory = str(kwargs['memory']) + "M"
+                vm['spec']['template']['spec']['domain']['resources'][
+                    'requests']['memory'] = memory
+        if 'cpu' in kwargs:
+            if kwargs['cpu'] is not None:
+                if kwargs['cpu'] < 10:
+                    cpu = str(kwargs['cpu'])
+                else:
+                    cpu = str(kwargs['cpu']) + "m"
+                vm['spec']['template']['spec']['domain']['cpu']['cores'] = cpu
+
+        disks = kwargs.get('disks', [])
+        i = 0
+        for disk in disks:
+            disk_type = disk['disk_type']
+            bus = disk.get('bus', 'virtio')
+            disk_name = disk.get('name', 'disk{}'.format(i))
+            i += 1
+            device = disk.get('device', 'disk')
+            if disk_type not in DISK_TYPES:
+                raise ValueError("The possible values for this "
+                                 "parameter are: ", DISK_TYPES)
+            # depending on disk_type, in the future,
+            # when more will be supported,
+            # additional elif should be added
+            if disk_type == "containerDisk":
+                try:
+                    image = disk['image']
+                except KeyError as exc:
+                    raise KeyError('A container disk needs a '
+                                   'containerized image')
+
+                volumes_dict = {'containerDisk': {'image': image},
+                                'name': disk_name}
+
+            if disk_type == "persistentVolumeClaim":
+                if 'claimName' in disk:
+                    claimName = disk['claimName']
+                    if claimName not in self.list_persistent_volume_claims(
+                        namespace=namespace
+                    ):
+                        if 'size' not in disk or "storageClassName" not in disk:
+                            msg = ("disk['size'] and "
+                                   "disk['storageClassName'] "
+                                   "are both required to create "
+                                   "a new claim.")
+                            raise KeyError(msg)
+                        size = disk['size']
+                        storage_class = disk['storageClassName']
+                        volume_mode = disk.get('volumeMode', 'Filesystem')
+                        access_mode = disk.get('accessMode', 'ReadWriteOnce')
+                        self.create_volume(size=size, name=claimName,
+                                           storageClassName=storage_class,
+                                           namespace=namespace,
+                                           volumeMode=volume_mode,
+                                           accessMode=access_mode)
+
+                else:
+                    msg = ("You must provide either a claimName of an "
+                           "existing claim or if you want one to be "
+                           "created you must additionally provide size "
+                           "and the storageClassName of the "
+                           "cluster, which allows dynamic provisioning, "
+                           "so a Persistent Volume Claim can be created. "
+                           "In the latter case please provide the desired "
+                           "size as well.")
+                    raise KeyError(msg)
+
+                volumes_dict = {'persistentVolumeClaim': {
+                                'claimName': claimName},
+                                'name': disk_name}
+            disk_dict = {device: {'bus': bus}, 'name': disk_name}
+            vm['spec']['template']['spec']['domain'][
+                'devices']['disks'].append(disk_dict)
+            vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # adding image in a container Disk
+        if 'image' not in kwargs:
+            raise KeyError("An 'image' keyword argument must be specified.")
+        image = kwargs['image']
+        if isinstance(image, NodeImage):
+            image = image.name
+        volumes_dict = {'containerDisk': {'image': image},
+                        'name': 'boot-disk'}
+        disk_dict = {'disk': {'bus': 'virtio'}, 'name': 'boot-disk'}
+        vm['spec']['template']['spec']['domain'][
+            'devices']['disks'].append(disk_dict)
+        vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # network
+        if 'network' in kwargs and kwargs['network']:
+            network = kwargs['network']
+            interface = network[1]
+            network_name = network[2]
+            network_type = network[0]
+        else:
+            interface = 'masquerade'
+            network_name = "netw1"
+            network_type = "pod"
+        network_dict = {network_type: {}, 'name': network_name}
+        interface_dict = {interface: {}, 'name': network_name}
+        vm['spec']['template']['spec'][
+            'networks'].append(network_dict)
+        vm['spec']['template']['spec']['domain']['devices'][
+            'interfaces'].append(interface_dict)
+
+        method = "POST"
+        data = json.dumps(vm)
+        req = KUBEVIRT_URL + "namespaces/" + namespace + "/virtualmachines/"
+        try:
+
+            self.connection.request(req, method=method, data=data)
+
+        except Exception as exc:
+            raise
+        # check if new node is present
+        nodes = self.list_nodes()
+        for node in nodes:
+            if node.name == name:
+                return node
+
+    def list_images(self, location=None):
+        """
+        If location (namespace) is provided only the images
+        in that location will be provided. Otherwise all of them.
+        """
+        nodes = self.list_nodes()
+        if location:
+            namespace = location.name
+            nodes = list(filter(lambda x: x['extra'][
+                                'namespace'] == namespace, nodes))
+
+        images = [node.image for node in nodes]
+        return images
+
+    def list_locations(self):
+        """
+        By locations here it is meant namespaces.
+        """
+        req = ROOT_URL + "namespaces"
+
+        namespaces = []
+        result = self.connection.request(req).object
+        for item in result['items']:
+            name = item['metadata']['name']
+            ID = item['metadata']['uid']
+            namespaces.append(NodeLocation(id=ID, name=name,
+                                           country='',
+                                           driver=self.connection.driver))
+        return namespaces
+
+    def list_sizes(self, location=None):
+
+        namespace = ''
+        if location:
+            namespace = location.name
+        nodes = self.list_nodes()
+        sizes = []
+        for node in nodes:
+            if not namespace:
+                sizes.append(node.size)
+            elif namespace == node.extra['namespace']:
+                sizes.append(node.size)
+
+        return sizes
+
+    def create_volume(self, size, name, storage_class_name,
+                      volume_mode='Filesystem', namespace='default',
+                      access_mode='ReadWriteOnce'):
+        """
+        Method to create a Persistent Volume Claim for storage,
+        thus storage is required in the arguments.
+
+        :param name: The name of the pvc an arbitrary string of lower letters
+        :type name: `str`
+
+        :param size: An int of the ammount of gigabytes desired
+        :type size: `int`
+
+        :param namespace: The namespace where the claim will live
+        :type namespace: `str`
+
+        :param storageClassName: If you want the pvc to be bound to
+                                 a particular class of PVs specified here.
+        :type storageClassName: `str`
+
+        :param accessMode: The desired access mode, ie "ReadOnlyMany"
+        :type accessMode: `str`
+
+        :param matchLabels: A dictionary with the labels, ie:
+                            {'release': 'stable,}
+        :type matchLabels: `dict` with keys `str` and values `str`
+        """
+        pvc = {
+            'apiVersion': 'v1',
+            'kind': 'PersistentVolumeClaim',
+            'metadata': {
+                'name': name
+            },
+            'spec': {
+                'accessModes': [],
+                'volumeMode': volume_mode,
+                'resources': {
+                    'requests': {
+                        'storage': ''
+                    }
+                },
+                'selector': {}
+            }
+        }
+
+        pvc['spec']['accessModes'].append(access_mode)
+
+        if storage_class_name is not None:
+            pvc['spec']['storageClassName'] = storage_class_name
+        else:
+            raise ValueError("The storage class name must be provided of a"
+                             "storage class which allows for dynamic "
+                             "provisioning")
+        pvc['spec']['resources']['requests']['storage'] = str(size) + 'Gi'
+
+        method = "POST"
+        req = ROOT_URL + "namespaces/" + namespace + "/persistentvolumeclaims"
+        data = json.dumps(pvc)
+        try:
+            result = self.connection.request(req, method=method, data=data)
+        except Exception as exc:
+            raise
+        if result.object['status']['phase'] != "Bound":
+            for _ in range(3):
+
+                req = ROOT_URL + "namespaces/" + namespace + \
+                    "/persistentvolumeclaims/" + name
+                try:
+                    result = self.connection.request(req).object
+                except Exception as exc:
+                    raise
+                if result['status']['phase'] == "Bound":
+                    break
+                time.sleep(3)
+
+        # check that the pv was created and bound
+        volumes = self.list_volumes()
+        for volume in volumes:
+            if volume.extra['pvc']['name'] == name:
+                return volume
+
+    def destroy_volume(self, volume):
+        # first delete the pvc
+        if volume.extra['isBound']:
+            pvc = volume.extra['pvc']['name']
+            namespace = volume.extra['pvc']['namespace']
+            method = 'DELETE'
+            req = ROOT_URL + "namespaces/" + namespace + \
+                "/persistentvolumeclaims/" + pvc
+            try:
+                result = self.connection.request(req, method=method)
+
+            except Exception as exc:
+                raise
+
+        pv = volume.name
+        req = ROOT_URL + "persistentvolumes/" + pv
+
+        try:
+            result = self.connection.request(req, method=method)
+            return result.status
+        except Exception as exc:
+            raise
+
+    def attach_volume(self, volume, ex_node, **kwargs):
+        """
+        kwargs: bus, name , device (disk or lun)
+        """
+        # volume must be bound to a claim
+        if not volume.extra['isBound']:
+            raise ValueError("""
+            This volume is not bound to a claim,
+            pick a volume that is or create a claim""")
+        claimName = volume.extra['pvc']['name']
+        name = kwargs.get('name', claimName)
+        bus = kwargs.get('bus', 'virtio')
+        device = kwargs.get('device', 'disk')
+        namespace = volume.extra['pvc']['namespace']
+        # check if vm is stopped
+        self.ex_stop_node(ex_node)
+        # check if it is the same namespace
+        if ex_node.extra['namespace'] != namespace:
+            msg = "The PVC and the VM must be in the same namespace"
+            raise ValueError(msg)
+        vm = ex_node.name
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + '/virtualmachines/'\
+            + vm
+        disk_dict = {device: {'bus': bus}, 'name': name}
+        volumes_dict = {'persistentVolumeClaim': {'claimName': claimName},
+                        'name': name}
+        # Get all the volumes of the vm
+        try:
+            result = self.connection.request(req).object
+        except Exception as exc:
+            raise
+        disks = result['spec']['template']['spec']['domain'][
+            'devices']['disks']
+        volumes = result['spec']['template']['spec']['volumes']
+        disks.append(disk_dict)
+        volumes.append(volumes_dict)
+        # now patch the new volumes and disks lists into the resource
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {'spec': {
+            'template': {
+                'spec': {
+                    'volumes': volumes,
+                    'domain': {
+                        'devices':
+                        {'disks': disks}
+                    }
+                }
+            }
+        }
+        }
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+            if 'pvcs' in ex_node.extra:
+                ex_node.extra['pvcs'].append(claimName)
+            else:
+                ex_node.extra['pvcs'] = [claimName]
+            return result in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    def detach_volume(self, volume, ex_node):
 
 Review comment:
   Same here - base API method only takes ``volume`` argument. If node argument is also needed, it should be left as as is (prefixed with ``ex_`` since it's indeed not a standard argument).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214412
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
+        # check if running
+        if node.state is NodeState.STOPPED:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace + \
+            '/virtualmachines/' + name
+        headers = {"Content-Type": "application/merge-patch+json"}
+        data = {"spec": {"running": False}}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def reboot_node(self, node):
+        """
+        Rebooting a node.
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        method = 'DELETE'
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachineinstances/' +
+                                             name,
+                                             method=method)
+
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as e:
+            raise
+        return
+
+    def destroy_node(self, node):
+        """
+        Terminating a VMI and deleting the VM resource backing it
+        """
+        namespace = node.extra['namespace']
+        name = node.name
+        # stop the vmi first
+        self.ex_stop_node(node)
+
+        try:
+            result = self.connection.request(KUBEVIRT_URL + 'namespaces/' +
+                                             namespace +
+                                             '/virtualmachines/' + name,
+                                             method='DELETE')
+            return result.status in VALID_RESPONSE_CODES
+        except Exception as exc:
+            raise
+
+    # only has container disk support atm with no persistency
+    def create_node(self, **kwargs):
+        """
+        Creating a VM with a containerDisk.
+        :param name: A name to give the VM. The VM will be identified by
+                     this name and atm it cannot be changed after it is set.
+        :type name: ``str``
+
+        :param namespace: The namespace where the VM will live.
+                          (default is 'default')
+        :type namespace: ``str``
+
+        :param image: It must be a Docker image with an embedded disk.
+                      May be a URI like `kubevirt/cirros-registry-disk-demo`,
+                      kubevirt will automatically pull it from
+                      https://hub.docker.com/u/URI.
+                      For more info visit:
+                      https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/disks-and-volumes.html#containerdisk
+        :type image: `str`
+
+        :param kwargs memory: The RAM in MB to be allocated to the VM
+        :type kwargs memory: ``int``
+
+        :param kwargs cpu: The ammount of cpu to be allocated in miliCPUs
+                    ie: 400 will mean 0.4 of a core, 1000 will mean 1 core
+                    and 3000 will mean 3 cores.
+        :type kwargs cpu: ``int``
+
+        :param kwargs disks: A list containing disk dictionaries.
+                             Each dictionaries should have the
+                             following optional keys:
+                             -bus: can be "virtio", "sata", or "scsi"
+                             -device: can be "lun" or "disk"
+                             The following are required keys:
+                             -disk_type: atm only "persistentVolumeClaim"
+                                         is supported
+                             -name: The name of the disk configuration
+                             -claimName: the name of the
+                                         Persistent Volume Claim
+
+                            If you wish a new Persistent Volume Claim can be
+                            created by providing the following:
+                            required:
+                            -size: the desired size (implied in GB)
+                            -storageClassName: the name of the storage class to
+                                               be used for the creation of the
+                                               Persistent Volume Claim.
+                                               Make sure it allows for
+                                               dymamic provisioning.
+                             optional:
+                            -accessMode: default is ReadWriteOnce
+                            -volumeMode: default is `Filesystem`,
+                                         it can also be `Block`
+
+        :type kwarg disks: `list` of `dict`. For each `dict` the types
+                            for its keys are:
+                            -bus: `str`
+                            -device: `str`
+                            -disk_type: `str`
+                            -name: `str`
+                            -claimName: `str`
+                            (for creating a claim:)
+                            -size: `int`
+                            -storageClassName: `str`
+                            -volumeMode: `str`
+                            -accessMode: `str`
+
+        :param kwargs network: Only the pod type is supported, and in the
+                               configuration masquerade or bridge are the
+                               accepted values.
+                               The parameter must be a tupple or list with
+                               (network_type, interface, name)
+        :param type: `iterable` (tupple or list) [network_type, inteface, name]
+                      network_type: `str` | only "pod" is accepted atm
+                      interface: `str` | "masquerade" or "bridge"
+                      name: `str`
+        """
+        # all valid disk types for which support will be added in the future
+        DISK_TYPES = {'containerDisk', 'ephemeral', 'configMap', 'dataVolume',
+                      'cloudInitNoCloud', 'persistentVolumeClaim', 'emptyDisk',
+                      'cloudInitConfigDrive', 'hostDisk'}
+
+        name = kwargs.get("name", "newVM")
+        namespace = kwargs.get('namespace', 'default')
+
+        terminationGracePeriod = kwargs.get('terminationGracePeriod', 0)
+
+        # vm template to be populated
+        vm = {
+            "apiVersion": "kubevirt.io/v1alpha3",
+            "kind": "VirtualMachine",
+            "metadata": {
+                "labels": {
+                    "kubevirt.io/vm": name
+                },
+                "name": name
+            },
+            "spec": {
+                "running": False,
+                "template": {
+                    "metadata": {
+                        "labels": {
+                            "kubevirt.io/vm": name
+                        }
+                    },
+                    "spec": {
+                        "domain": {
+                            "cpu": {},
+                            "devices": {
+                                "disks": [],
+                                "interfaces": [],
+                                "networkInterfaceMultiqueue": False,
+
+                            },
+                            "machine": {
+                                "type": ""
+                            },
+                            "resources": {
+                                "requests": {},
+                            },
+                        },
+                        "networks": [],
+                        "terminationGracePeriodSeconds": terminationGracePeriod,
+                        "volumes": []
+                    }
+                }
+            }
+        }
+
+        if 'memory' in kwargs:
+            if kwargs['memory'] is not None:
+                memory = str(kwargs['memory']) + "M"
+                vm['spec']['template']['spec']['domain']['resources'][
+                    'requests']['memory'] = memory
+        if 'cpu' in kwargs:
+            if kwargs['cpu'] is not None:
+                if kwargs['cpu'] < 10:
+                    cpu = str(kwargs['cpu'])
+                else:
+                    cpu = str(kwargs['cpu']) + "m"
+                vm['spec']['template']['spec']['domain']['cpu']['cores'] = cpu
+
+        disks = kwargs.get('disks', [])
+        i = 0
+        for disk in disks:
+            disk_type = disk['disk_type']
+            bus = disk.get('bus', 'virtio')
+            disk_name = disk.get('name', 'disk{}'.format(i))
+            i += 1
+            device = disk.get('device', 'disk')
+            if disk_type not in DISK_TYPES:
+                raise ValueError("The possible values for this "
+                                 "parameter are: ", DISK_TYPES)
+            # depending on disk_type, in the future,
+            # when more will be supported,
+            # additional elif should be added
+            if disk_type == "containerDisk":
+                try:
+                    image = disk['image']
+                except KeyError as exc:
+                    raise KeyError('A container disk needs a '
+                                   'containerized image')
+
+                volumes_dict = {'containerDisk': {'image': image},
+                                'name': disk_name}
+
+            if disk_type == "persistentVolumeClaim":
+                if 'claimName' in disk:
+                    claimName = disk['claimName']
+                    if claimName not in self.list_persistent_volume_claims(
+                        namespace=namespace
+                    ):
+                        if 'size' not in disk or "storageClassName" not in disk:
+                            msg = ("disk['size'] and "
+                                   "disk['storageClassName'] "
+                                   "are both required to create "
+                                   "a new claim.")
+                            raise KeyError(msg)
+                        size = disk['size']
+                        storage_class = disk['storageClassName']
+                        volume_mode = disk.get('volumeMode', 'Filesystem')
+                        access_mode = disk.get('accessMode', 'ReadWriteOnce')
+                        self.create_volume(size=size, name=claimName,
+                                           storageClassName=storage_class,
+                                           namespace=namespace,
+                                           volumeMode=volume_mode,
+                                           accessMode=access_mode)
+
+                else:
+                    msg = ("You must provide either a claimName of an "
+                           "existing claim or if you want one to be "
+                           "created you must additionally provide size "
+                           "and the storageClassName of the "
+                           "cluster, which allows dynamic provisioning, "
+                           "so a Persistent Volume Claim can be created. "
+                           "In the latter case please provide the desired "
+                           "size as well.")
+                    raise KeyError(msg)
+
+                volumes_dict = {'persistentVolumeClaim': {
+                                'claimName': claimName},
+                                'name': disk_name}
+            disk_dict = {device: {'bus': bus}, 'name': disk_name}
+            vm['spec']['template']['spec']['domain'][
+                'devices']['disks'].append(disk_dict)
+            vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # adding image in a container Disk
+        if 'image' not in kwargs:
+            raise KeyError("An 'image' keyword argument must be specified.")
+        image = kwargs['image']
+        if isinstance(image, NodeImage):
+            image = image.name
+        volumes_dict = {'containerDisk': {'image': image},
+                        'name': 'boot-disk'}
+        disk_dict = {'disk': {'bus': 'virtio'}, 'name': 'boot-disk'}
+        vm['spec']['template']['spec']['domain'][
+            'devices']['disks'].append(disk_dict)
+        vm['spec']['template']['spec']['volumes'].append(volumes_dict)
+
+        # network
+        if 'network' in kwargs and kwargs['network']:
+            network = kwargs['network']
+            interface = network[1]
+            network_name = network[2]
+            network_type = network[0]
+        else:
+            interface = 'masquerade'
+            network_name = "netw1"
+            network_type = "pod"
+        network_dict = {network_type: {}, 'name': network_name}
+        interface_dict = {interface: {}, 'name': network_name}
+        vm['spec']['template']['spec'][
+            'networks'].append(network_dict)
+        vm['spec']['template']['spec']['domain']['devices'][
+            'interfaces'].append(interface_dict)
+
+        method = "POST"
+        data = json.dumps(vm)
+        req = KUBEVIRT_URL + "namespaces/" + namespace + "/virtualmachines/"
+        try:
+
+            self.connection.request(req, method=method, data=data)
+
+        except Exception as exc:
+            raise
+        # check if new node is present
+        nodes = self.list_nodes()
+        for node in nodes:
+            if node.name == name:
+                return node
+
+    def list_images(self, location=None):
+        """
+        If location (namespace) is provided only the images
+        in that location will be provided. Otherwise all of them.
+        """
+        nodes = self.list_nodes()
+        if location:
+            namespace = location.name
+            nodes = list(filter(lambda x: x['extra'][
+                                'namespace'] == namespace, nodes))
+
+        images = [node.image for node in nodes]
+        return images
+
+    def list_locations(self):
+        """
+        By locations here it is meant namespaces.
+        """
+        req = ROOT_URL + "namespaces"
+
+        namespaces = []
+        result = self.connection.request(req).object
+        for item in result['items']:
+            name = item['metadata']['name']
+            ID = item['metadata']['uid']
+            namespaces.append(NodeLocation(id=ID, name=name,
+                                           country='',
+                                           driver=self.connection.driver))
+        return namespaces
+
+    def list_sizes(self, location=None):
+
+        namespace = ''
+        if location:
+            namespace = location.name
+        nodes = self.list_nodes()
+        sizes = []
+        for node in nodes:
+            if not namespace:
+                sizes.append(node.size)
+            elif namespace == node.extra['namespace']:
+                sizes.append(node.size)
+
+        return sizes
+
+    def create_volume(self, size, name, storage_class_name,
+                      volume_mode='Filesystem', namespace='default',
+                      access_mode='ReadWriteOnce'):
+        """
+        Method to create a Persistent Volume Claim for storage,
+        thus storage is required in the arguments.
+
+        :param name: The name of the pvc an arbitrary string of lower letters
+        :type name: `str`
+
+        :param size: An int of the ammount of gigabytes desired
+        :type size: `int`
+
+        :param namespace: The namespace where the claim will live
+        :type namespace: `str`
+
+        :param storageClassName: If you want the pvc to be bound to
+                                 a particular class of PVs specified here.
+        :type storageClassName: `str`
+
+        :param accessMode: The desired access mode, ie "ReadOnlyMany"
+        :type accessMode: `str`
+
+        :param matchLabels: A dictionary with the labels, ie:
+                            {'release': 'stable,}
+        :type matchLabels: `dict` with keys `str` and values `str`
+        """
+        pvc = {
+            'apiVersion': 'v1',
+            'kind': 'PersistentVolumeClaim',
+            'metadata': {
+                'name': name
+            },
+            'spec': {
+                'accessModes': [],
+                'volumeMode': volume_mode,
+                'resources': {
+                    'requests': {
+                        'storage': ''
+                    }
+                },
+                'selector': {}
+            }
+        }
+
+        pvc['spec']['accessModes'].append(access_mode)
+
+        if storage_class_name is not None:
+            pvc['spec']['storageClassName'] = storage_class_name
+        else:
+            raise ValueError("The storage class name must be provided of a"
+                             "storage class which allows for dynamic "
+                             "provisioning")
+        pvc['spec']['resources']['requests']['storage'] = str(size) + 'Gi'
+
+        method = "POST"
+        req = ROOT_URL + "namespaces/" + namespace + "/persistentvolumeclaims"
+        data = json.dumps(pvc)
+        try:
+            result = self.connection.request(req, method=method, data=data)
+        except Exception as exc:
+            raise
+        if result.object['status']['phase'] != "Bound":
+            for _ in range(3):
+
+                req = ROOT_URL + "namespaces/" + namespace + \
+                    "/persistentvolumeclaims/" + name
+                try:
+                    result = self.connection.request(req).object
+                except Exception as exc:
+                    raise
+                if result['status']['phase'] == "Bound":
+                    break
+                time.sleep(3)
+
+        # check that the pv was created and bound
+        volumes = self.list_volumes()
+        for volume in volumes:
+            if volume.extra['pvc']['name'] == name:
+                return volume
+
+    def destroy_volume(self, volume):
+        # first delete the pvc
+        if volume.extra['isBound']:
+            pvc = volume.extra['pvc']['name']
+            namespace = volume.extra['pvc']['namespace']
+            method = 'DELETE'
+            req = ROOT_URL + "namespaces/" + namespace + \
+                "/persistentvolumeclaims/" + pvc
+            try:
+                result = self.connection.request(req, method=method)
+
+            except Exception as exc:
+                raise
+
+        pv = volume.name
+        req = ROOT_URL + "persistentvolumes/" + pv
+
+        try:
+            result = self.connection.request(req, method=method)
+            return result.status
+        except Exception as exc:
+            raise
+
+    def attach_volume(self, volume, ex_node, **kwargs):
 
 Review comment:
   Please make sure the method signature matches the base API one and that the non standard arguments are prefixed with ``ex_``.
   
   Base signature looks like this:
   
   ```python
   def attach_volume(self, node, volume, device=None):
   ```
   
   So ``node`` argument needs to be first, etc.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213910
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
 
 Review comment:
   Since ``ca_cert`` is set to None by default it might not be a bad idea to emit a warning if ca cert is not used that server SSL certificate won't be validated against the CA cert.
   
   And when the documentation is added, it would also be good to point that out there.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Eis-D-Z commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Eis-D-Z commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-577745536
 
 
   Fixed all the commented segments and did a wrong push which I undid subsequently.
   Thank you for your patience!

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361213476
 
 

 ##########
 File path: libcloud/http.py
 ##########
 @@ -194,20 +194,22 @@ def __init__(self, host, port, secure=None, **kwargs):
         http_proxy_url_env = os.environ.get(HTTP_PROXY_ENV_VARIABLE_NAME,
                                             https_proxy_url_env)
 
-        # Connection argument rgument has precedence over environment variables
+        # Connection argument has precedence over environment variables
         proxy_url = kwargs.pop('proxy_url', http_proxy_url_env)
 
         self._setup_verify()
         self._setup_ca_cert()
 
         LibcloudBaseConnection.__init__(self)
 
+        self.session.timeout = kwargs.pop('timeout', 60)
 
 Review comment:
   Does this change the default ``requests`` timeout behavior?
   
   If so, we should probably only set that attribute if ``timeout`` is explicitly specified (for backward compatibility reasons and to make sure it doesn't negatively affect long running and streaming connections)?

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on issue #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on issue #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#issuecomment-577830504
 
 
   Thanks for addressing the comments.
   
   There were still some issues I fixed (abf36eb1cf8764ab5d35771ac85265c2c5d34f2c, d1b28ad955f3cb4cba3a7eb99e46fc15dcbacc63, 4ad7558fd056c13f2df1c3213cf95e50fca5df13).
   
   Next time please make sure the CI / build passes.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214065
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
 
 Review comment:
   This method name can now be changed to ``start_node()`` (see https://github.com/apache/libcloud/pull/1375 for details).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

[GitHub] [libcloud] Kami commented on a change in pull request #1394: Add KubeVirt driver & tests

Posted by GitBox <gi...@apache.org>.
Kami commented on a change in pull request #1394: Add KubeVirt driver & tests
URL: https://github.com/apache/libcloud/pull/1394#discussion_r361214089
 
 

 ##########
 File path: libcloud/compute/drivers/kubevirt.py
 ##########
 @@ -0,0 +1,996 @@
+"""
+kubevirt driver with support for nodes (vms)
+"""
+import os
+import json
+import time
+from datetime import datetime
+
+import libcloud.security
+
+
+from libcloud.container.drivers.kubernetes import KubernetesResponse
+from libcloud.container.drivers.kubernetes import KubernetesConnection
+from libcloud.container.drivers.kubernetes import VALID_RESPONSE_CODES
+
+from libcloud.common.base import KeyCertificateConnection, ConnectionKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.compute.types import Provider, NodeState
+from libcloud.compute.base import NodeDriver, NodeSize, Node
+from libcloud.compute.base import NodeImage, NodeLocation, StorageVolume
+
+__all__ = [
+    "KubernetesTLSConnection",
+    "KubernetesTokenAuthentication",
+    "KubeVirtNode",
+    "KubeVirtNodeDriver"
+]
+ROOT_URL = '/api/v1/'
+KUBEVIRT_URL = '/apis/kubevirt.io/v1alpha3/'
+
+
+class KubernetesTLSConnection(KeyCertificateConnection):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def __init__(self, key, secure=True, host='localhost',
+                 port='6443', key_file=None, cert_file=None, ca_cert='',
+                 **kwargs):
+
+        super(KubernetesTLSConnection, self).__init__(key_file=key_file,
+                                                      cert_file=cert_file,
+                                                      secure=secure, host=host,
+                                                      port=port, url=None,
+                                                      proxy_url=None,
+                                                      timeout=None,
+                                                      backoff=None,
+                                                      retry_delay=None)
+        if key_file:
+            keypath = os.path.expanduser(key_file)
+            is_file_path = os.path.exists(keypath) and os.path.isfile(keypath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an key PEM file to authenticate with '
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/')
+            self.key_file = key_file
+            certpath = os.path.expanduser(cert_file)
+            is_file_path = os.path.exists(
+                certpath) and os.path.isfile(certpath)
+            if not is_file_path:
+                raise InvalidCredsError(
+                    'You need an certificate PEM file to authenticate'
+                    'via tls. For more info please visit:'
+                    'https://kubernetes.io/docs/concepts/cluster-administration/certificates/'
+                )
+
+            self.cert_file = cert_file
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class KubernetesTokenAuthentication(ConnectionKey):
+    responseCls = KubernetesResponse
+    timeout = 60
+
+    def add_default_headers(self, headers):
+        if 'Content-Type' not in headers:
+            headers['Content-Type'] = 'application/json'
+        if self.key:
+            headers['Authorization'] = 'Bearer ' + self.key
+        else:
+            raise ValueError("Please provide a valid token in the key param")
+        return headers
+
+
+class KubeVirtNode(Node):
+
+    def start_node(self):
+        self.driver.ex_start_node(self)
+
+    def stop_node(self):
+        self.driver.ex_stop_node(self)
+
+
+class KubeVirtNodeDriver(NodeDriver):
+    type = Provider.KUBEVIRT
+    name = "kubevirt"
+    website = 'https://www.kubevirt.io'
+    connectionCls = KubernetesConnection
+
+    NODE_STATE_MAP = {
+        'pending': NodeState.PENDING,
+        'running': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED
+    }
+
+    def __init__(self, key=None, secret=None, secure=True, host="localhost",
+                 port=6443, key_file=None, cert_file=None, ca_cert='',
+                 token_bearer_auth=False, verify=True):
+
+        libcloud.security.VERIFY_SSL_CERT = verify
+        if token_bearer_auth:
+            self.connectionCls = KubernetesTokenAuthentication
+            if not key:
+                raise ValueError("The token must be a string")
+            secure = True
+
+        if key_file:
+            self.connectionCls = KubernetesTLSConnection
+            self.key_file = key_file
+            self.cert_file = cert_file
+            secure = True
+
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.lstrip(prefix)
+
+        super(KubeVirtNodeDriver, self).__init__(key=key,
+                                                 secret=secret,
+                                                 secure=secure,
+                                                 host=host,
+                                                 port=port,
+                                                 key_file=key_file,
+                                                 cert_file=cert_file)
+
+        # check if both key and cert files are present
+        if key_file or cert_file:
+            if not(key_file and cert_file):
+                raise Exception("Both key and certificate files are needed")
+
+        if ca_cert:
+            self.connection.connection.ca_cert = ca_cert
+        else:
+            # do not verify SSL certificate
+            self.connection.connection.ca_cert = False
+
+        self.connection.secure = secure
+        self.connection.host = host
+        self.connection.port = port
+
+        if self.connectionCls == KubernetesConnection:
+            self.connection.secret = secret
+        self.connection.key = key
+
+    def list_nodes(self, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces.append(namespace)
+        else:
+            for ns in self.list_locations():
+                namespaces.append(ns.name)
+
+        dormant = []
+        live = []
+        for ns in namespaces:
+            req = KUBEVIRT_URL + 'namespaces/' + ns + \
+                "/virtualmachines"
+            result = self.connection.request(req)
+            if result.status != 200:
+                continue
+            result = result.object
+            for item in result['items']:
+                if not item['spec']['running']:
+                    dormant.append(item)
+                else:
+                    live.append(item)
+        vms = []
+        for vm in dormant:
+            vms.append(self._to_node(vm, is_stopped=True))
+
+        for vm in live:
+            vms.append(self._to_node(vm, is_stopped=False))
+
+        return vms
+
+    def get_node(self, id=None, name=None):
+        "get a vm by name or id"
+        if not id and not name:
+            raise ValueError("This method needs id or name to be specified")
+        nodes = self.list_nodes()
+        if id:
+            node_gen = filter(lambda x: x.id == id,
+                              nodes)
+        if name:
+            node_gen = filter(lambda x: x.name == name,
+                              nodes)
+
+        try:
+            return next(node_gen)
+        except StopIteration:
+            raise ValueError("Node does not exist")
+
+    def ex_start_node(self, node):
+        # make sure it is stopped
+        if node.state is NodeState.RUNNING:
+            return True
+        name = node.name
+        namespace = node.extra['namespace']
+        req = KUBEVIRT_URL + 'namespaces/' + namespace +\
+            '/virtualmachines/' + name
+        data = {"spec": {"running": True}}
+        headers = {"Content-Type": "application/merge-patch+json"}
+        try:
+            result = self.connection.request(req, method="PATCH",
+                                             data=json.dumps(data),
+                                             headers=headers)
+
+            return result.status in VALID_RESPONSE_CODES
+
+        except Exception as exc:
+            raise
+
+    def ex_stop_node(self, node):
 
 Review comment:
   This method name can now be changed to ``stop_node()`` (see https://github.com/apache/libcloud/pull/1375 for details).

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services