Example #1
0
 def get_creds(self):
     """Load credentials such as a subscription_id and a certificate path in
     a local system. These information should be set by the azure-cli
     tool."""
     self.cert = Credentials()
     self.subscription_id = self.cert.getSubscription()
     self.certificate_path = self.cert.getManagementCertFile()
Example #2
0
class SimpleAzure:
    """Constructs a :class:`SimpleAzure <SimpleAzure>`.
    Returns :class:`SimpleAzure <SimpleAzure>` instance.

    Usage::

        >>> from simpleazure.simpleazure import SimpleAzure as saz
        >>> azure = saz()
        >>> azure.get_config() # Load credentials
        >>> azure.create_vm()
        <azure.servicemanagement.AsynchronousOperationResult at 0x2945e10>
    """

    subscription_id = ""
    certificate_path = ""

    # ServiceManagementService
    sms = None

    # default value
    name = "sazvm-12345"
    location = "East US"

    cluster_name_prefix = "sazvm-cluster-"

    image = None
    image_name = ""
    _image_name = {"os": "Linux", "category": "Canonical", "label": "12.04"}
    os_name = None

    # Storage
    storage_account = ""
    container = "os-image"
    blob = ""
    blob_ext = ".vhd"
    windows_blob_url = "blob.core.windows.net"
    media_link = ""

    # Linux
    linux_user_id = "azureuser"
    linux_user_passwd = "mypassword1234@"  # Not used in ssh

    # SSH Keys
    # http://msdn.microsoft.com/en-us/library/windowsazure/jj157194.aspx#SSH
    azure_config = config.azure_path()
    thumbprint_path = azure_config + "/.ssh/thumbprint"
    authorized_keys = "/home/" + linux_user_id + "/.ssh/authorized_keys"
    # public_key_path = azure_config + '/.ssh/myCert.pem'
    private_key_path = azure_config + "/.ssh/myPrivateKey.key"
    key_pair_path = private_key_path

    # Adding for cluster
    num_4_win = 0
    num_4_lin = 4
    cluster_count = num_4_win + num_4_lin

    def __init__(self):
        """Initialize variables"""
        self.set_name()
        self.set_location()
        self.set_role_size()

    def set_name(self, name=None):
        """Set a name of virtual machine. If name is not specified, random name
        will be generated in form of 'sazvm-' following with five digits.
        
        :param name: the name to use for a virtual machine
        :type name: str.

        """
        if not name:
            name = "sazvm-" + self.get_random()
        self.name = name

    def get_name(self):
        """Return a name of the virtual machine.

        :returns: str

        """
        return self.name

    def get_random(self):
        """Return a random string in a five digits.

        :returns: str

        """
        return "".join(str(x) for x in random.sample(range(0, 10), 5))

    def set_location(self, location=config.DEFAULT_LOCATION):
        """Set a location for the virtual machine among available locations.

        :param location: the name of a location to use
        :type location: str.

        """

        self.location = location

    def get_location(self):
        return self.location

    def set_role_size(self, size=config.DEFAULT_ROLE_SIZE):
        """Set a role size for the virtual machine among ExtraSmall, Small,
        Medium, Large, ExtraLarge

        :param size: the name of a role size to create
        :type size: str.

        ref:
        http://msdn.microsoft.com/en-us/library/windowsazure/jj157194.aspx#bk_role

        """
        self.role_size = size

    def get_role_size(self):
        """Return a role size of the virtual machine.

        "returns: str

        """
        return self.role_size

    def get_config(self):
        """Load configurations for the virtual machine. For example, credentials
        should be loaded to connect Windows Azure Services."""

        self.get_creds()

    def get_creds(self):
        """Load credentials such as a subscription_id and a certificate path in
        a local system. These information should be set by the azure-cli
        tool."""
        self.cert = Credentials()
        self.subscription_id = self.cert.getSubscription()
        self.certificate_path = self.cert.getManagementCertFile()

    def create_vm(self, name=None, location=None):
        """Create a Window Azure Virtual Machine

        :param name: (optional) the name of a virtual machine to use.
        :type name: str.
        :param location: (optional) the name of a location to use for the
        virtual machine.
        :type location: str.
        :returns: azure.servicemanagement.AsynchronousOperationResult

        """
        self.set_name(name)
        self.connect_service()
        self.create_cloud_service(name, location)
        self.set_image()
        self.get_media_link(blobname=name)

        os_hd = OSVirtualHardDisk(self.image_name, self.media_link)
        linux_config = LinuxConfigurationSet(self.get_name(), self.linux_user_id, self.linux_user_passwd, True)

        self.set_ssh_keys(linux_config)
        self.set_network()
        self.set_service_certs()
        # can't find certificate right away.
        sleep(5)

        result = self.sms.create_virtual_machine_deployment(
            service_name=self.get_name(),
            deployment_name=self.get_name(),
            deployment_slot="production",
            label=self.get_name(),
            role_name=self.get_name(),
            system_config=linux_config,
            os_virtual_hard_disk=os_hd,
            network_config=self.network,
            role_size=self.get_role_size(),
        )

        self.result = result
        return result

    def connect_service(self, refresh=False):
        """Connect Windows Azure Service via ServiceManagementService() with a
        subscription id and a certificate. 
        
        :param refesh: (optional) the connection will be update if refresh is
        set
        :type refresh: bool.
        
        """
        if not self.sms or refresh:
            self.sms = ServiceManagementService(self.subscription_id, self.certificate_path)

    def create_cloud_service(self, name=None, location=None):
        """Create a cloud (hosted) service via create_hosted_service()

        :param name: (optional) the name of a cloud service to use
        :type name: str.
        :param location: (optional) the name of a location for the cloud service 
        :type location: str.

        """
        if not name:
            name = self.get_name()
        if not location:
            location = self.location
        self.sms.create_hosted_service(service_name=name, label=name, location=location)

    def set_ssh_keys(self, config):
        """Configure login credentials with ssh keys for the virtual machine.
        This is only for linux OS, not Windows.

        :param config: the return value of LinuxConfigurationSet()
        :type config: class LinuxConfigurationSet

        """

        # fingerprint captured by 'openssl x509 -in myCert.pem -fingerprint
        # -noout|cut -d"=" -f2|sed 's/://g'> thumbprint'
        # (Sample output) C453D10B808245E0730CD023E88C5EB8A785ED6B
        self.thumbprint = open(self.thumbprint_path, "r").readline().split("\n")[0]
        publickey = PublicKey(self.thumbprint, self.authorized_keys)
        # KeyPair is a SSH kay pair both a public and a private key to be stored
        # on the virtual machine.
        # http://msdn.microsoft.com/en-us/library/windowsazure/jj157194.aspx#SSH
        keypair = KeyPair(self.thumbprint, self.key_pair_path)
        config.ssh.public_keys.public_keys.append(publickey)
        config.ssh.key_pairs.key_pairs.append(keypair)

        # Note
        # Since PKCS#10 X.509 is not fully supported by pycrypto, paramiko can
        # not use the key generated with PKCS, for example, openssl req ...
        # To do bypass, ssh-keygen can be used in the following order
        #
        # Generate a key pair
        # 1. ssh-keygen -f myPrivateKey.key (default is rsa and 2048 bits)
        #
        # Get certificate from a private key
        # 2. openssl req -x509 -nodes -days 365 -new -key myPrivateKey.key
        # -out myCert.pem
        #
        # .cer can be generated
        # openssl x509 -outform der -in public_key_file.pem -out myCert.cer
        #
        # .pfx
        # openssl pkcs12 -in public_key_file.pem -inkey private_key_file.key
        # -export -out myCert.pfx

        # Reference:
        # http://www.sslshopper.com/article-most-common-openssl-commands.html

    def set_network(self):
        """Configure network for a virtual machine.
        End Points (ports) can be opened through this function.
        For example, opening ssh(22) port will be configured.

        """
        network = ConfigurationSet()
        network.configuration_set_type = "NetworkConfiguration"
        network.input_endpoints.input_endpoints.append(ConfigurationSetInputEndpoint("ssh", "tcp", "22", "22"))
        self.network = network

    def set_service_certs(self):
        """Add a certificate to cloud (hosted) service.
        Personal Information Exchange (.pfx) should exist in the azure config
        directory (e.g. $HOME/.azure/.ssh/myCert.pfx). Python SDK only support
        .pfx at this time.

        """
        # command used:
        # openssl pkcs12 -in myCert.pem -inkey myPrivateKey.key
        # -export -out myCert.pfx
        cert_data_path = self.azure_config + "/.ssh/myCert.pfx"
        with open(cert_data_path, "rb") as bfile:
            cert_data = base64.b64encode(bfile.read())

        cert_format = "pfx"
        cert_password = ""
        cert_res = self.sms.add_service_certificate(
            service_name=self.get_name(), data=cert_data, certificate_format=cert_format, password=cert_password
        )
        self.cert_return = cert_res

    def list_images(self):
        """List available operating systems images on Windows Azure.

        :returns: dict.

        """
        return self.get_registered_image()

    def get_registered_image(self, label=None, name=None):
        """Return available operating systems images on Windows Azure

        :param label: (optional) a simple description of images. not unique
        value
        :type label: str.
        :param name: (optional) a unique name of images.
        :type name: str.
        :returns: dict.

        """

        # Establish connection, if not exist
        self.connect_service()
        images = self.sms.list_os_images()
        if not label and not name:
            return images

        for image in images:
            if name and image.name == name:
                return image
            if label and image.label == label:
                lastone = image

        if lastone:
            return lastone

        return images

    def list_storage_accounts(self):
        """List available storage accounts for the subscription id.

        :returns: dict.

        """
        self.connect_service()
        return self.sms.list_storage_accounts()

    def get_status(self, request_id=None):
        """Return a status of a deployed virtual machine with a request id.

        :param request_id: (optional) a request id to look up.
        :type request_id: str.
        :returns: dict.

        """
        if not request_id:
            request_id = self.result.request_id
        self.connect_service()
        return self.sms.get_operation_status(request_id)

    def get_deployment(self):
        """Return a detailed information of a deployment with the name.

        :returns: dict.

        """
        self.connect_service()
        return self.sms.get_deployment_by_name(service_name=self.get_name(), deployment_name=self.get_name())

    def list_deployments(self):
        """Return a list of deployments

        :returns: dict.

        """
        self.connect_service()
        return self.sms.list_hosted_services()

    def set_image(self, name=None, image=None, refresh=False):
        """Set os image to deploy virtual machines.
        self.image_name and self.os_name will be set

        :param name: (optional) an image name to set
        :type name: str.
        :param image: (optional) an oject of an image
        :type image: obj. (azure.servicemanagement.OSImage) 
        :param refresh: (optional) reset an image name
        :type refresh: bool.

        """
        # if set then skip unless it's forced.
        if self.image_name and not refresh:
            return

        # get a default image
        if not name and not image:
            image = self.get_registered_image(label=config.DEFAULT_IMAGE_LABEL)

        # set image
        if image:
            self.image = image
            self.image_name = image.name
            self.os_name = image.os

    def get_media_link(self, storage_account=None, container=None, blobname=None):
        """Return a media link in http URL.
        
        :param storage_account: (optional) a name of a storage account to use.
        :type storage_account: str.
        :param container: (optional) a name of a container to use.
        :type container: str.
        :param blobname: (optional) a name of a blob file to use.
        :type blobname: str.
        :returns: str
        
        """
        if not storage_account:
            storage_account = self.get_storage_account()
        if not blobname:
            blobname = self.get_name()
        if not container:
            container = self.container
        blob_prefix = self.os_name
        blob = blob_prefix + "-" + blobname + self.blob_ext
        media_link = "http://" + storage_account + "." + self.windows_blob_url + "/" + container + "/" + blob
        self.media_link = media_link
        return media_link

    def get_storage_account(self, refresh=False):
        """Return a storage account.

        If there is a selected image to deploy, the same storage account will be
        used with the image's one.
        Otherwise, the last storage account of a subscription id will be used.

        Note. 
        The disk's VHD must be in the same account as the VHD of the source
        image (source account: xxx.blob.core.windows.net, target
        account: xxx.blob.core.windows.net).

        """

        if self.image and self.image.media_link:
            account_name = self.get_account_from_link(self.image.media_link)
        else:
            account_name = self.get_last_storage_account()

        self.storage_account = account_name
        return account_name

    def get_last_storage_account(self, refresh=False):
        """Return the last storage account name of a subscription id
 
        :param refesh: (optional) the storage account name will be update if
        refresh is set
        :type refresh: bool.
        :returns: str.

        """
        self.connect_service()
        if not self.storage_account or refresh:
            result = self.sms.list_storage_accounts()
            for account in result:
                storage_account = account.service_name
        try:
            return storage_account
        except:
            self.create_storage_account()
            storage_account = self.get_name()
            return storage_account

    def create_storage_account(self):
        name = self.get_name()[:24].replace("-", "")
        description = name + "description"
        label = name + "label"
        self.sms.create_storage_account(
            service_name=name, description=description, label=label, location=self.get_location()
        )

    def get_account_from_link(self, url):
        """Return hostname from a link.
        top hostname indicates a storage account name

        :param url: a media_link
        :type url: str.

        """
        try:
            o = urlparse(url)
            host = o.hostname.split(".")[0]
        except:
            host = None

        return host

    def create_cluster(self, num=None, option=None):
        """Create multiple virtual machines to support cluster computing
        
        :param num: (optional) the number of virtual machines to create. Default
        is 4.
        :type num: int.
        :param option: (optional) the detailed information for cluster nodes to
        create.
        :type option: dict.
        :returns: list.

        """

        cluster_count = num
        if not num:
            cluster_count = self.cluster_count
        # results = []
        results = {}
        # It is supposed to use multi-processing instead of for loop
        for cnt in range(cluster_count):
            self.set_name(self.cluster_name_prefix + str(cnt) + "-" + self.get_random())
            result = self.create_vm(self.get_name())
            sleep(10)
            # results.append(result)
            results[self.get_name()] = result
            if cnt == 0:
                results["master"] = self.get_name()

            # media_link = self.get_media_link(blobname=new_name)
            # self.create_cloud_service(name=new_name)
            # temporarily added waiting time for deploying cloud service
        self.results = results
        return results

    def login_to(self, name=None):
        """SSH to a virtual machine
        
        :param name: (optional) the hostname of a virtual machine
        :type name: str.
        :returns: ssh object.
        
        """

        if not name:
            name = self.name
        hostname = config.get_azure_domain(name)

        sshmaster = ssh.SSH()
        sshmaster.setup(host=hostname, pkey=self.private_key_path)
        sshmaster.shell()

    def get_username(self):
        return self.linux_user_id

    def get_pkey(self):
        return self.private_key_path