Example #1
0
    def list_nodes(self):
        """
        Retrieves the list of nodes that have been defined across
        blueprints for this facility

        :return: names of nodes defined for this facility
        :rtype: ``list`` of ``str`` or ``[]``

        Nodes are defined in blueprints.

        """

        labels = []

        for blueprint in self.blueprints:
            name = list(blueprint)[0]
            if 'nodes' in blueprint[name]:
                for item in blueprint[name]['nodes']:
                    if type(item) is dict:
                        label = list(item)[0]

                    else:
                        label = item

                    for label in PlumberyNodes.expand_labels(label):
                        if label in labels:
                            plogging.warning("Duplicate node name '{}'"
                                             .format(label))
                        else:
                            labels.append(label)

        return labels
Example #2
0
    def upgrade_vmware_tools(self, node):
        """
        Upgrade VMware tools on target node

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        """

        if self.engine.safeMode:
            return True

        while True:
            try:
                self.region.ex_update_vm_tools(node=node)

                plogging.info("- upgrading vmware tools")
                return True

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                if 'Please try again later' in str(feedback):
                    time.sleep(10)
                    continue

                if 'NO_CHANGE' in str(feedback):
                    plogging.debug("- vmware tools is already up-to-date")
                    return True

                plogging.warning("- unable to upgrade vmware tools")
                plogging.warning(str(feedback))
                return False
    def upgrade_vmware_tools(self, node):
        """
        Upgrade VMware tools on target node

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        """

        if self.engine.safeMode:
            return True

        while True:
            try:
                self.region.ex_update_vm_tools(node=node)

                plogging.info("- upgrading vmware tools")
                return True

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                if 'Please try again later' in str(feedback):
                    time.sleep(10)
                    continue

                if 'NO_CHANGE' in str(feedback):
                    plogging.debug("- vmware tools is already up-to-date")
                    return True

                plogging.warning("- unable to upgrade vmware tools")
                plogging.warning(str(feedback))
                return False
Example #4
0
    def list_nodes(self):
        """
        Retrieves the list of nodes that have been defined across
        blueprints for this facility

        :return: names of nodes defined for this facility
        :rtype: ``list`` of ``str`` or ``[]``

        Nodes are defined in blueprints.

        """

        labels = []

        for blueprint in self.blueprints:
            name = list(blueprint)[0]
            if 'nodes' in blueprint[name]:
                for item in blueprint[name]['nodes']:
                    if type(item) is dict:
                        label = list(item)[0]

                    else:
                        label = item

                    for label in PlumberyNodes.expand_labels(label):
                        if label in labels:
                            plogging.warning(
                                "Duplicate node name '{}'".format(label))
                        else:
                            labels.append(label)

        return labels
Example #5
0
 def test_direct(self):
     plogging.setLevel(logging.DEBUG)
     self.assertEqual(plogging.getEffectiveLevel(), logging.DEBUG)
     plogging.debug("hello world -- debug")
     plogging.info("hello world -- info")
     plogging.warning("hello world -- warning")
     plogging.error("hello world -- error")
     plogging.critical("hello world -- critical")
     self.assertEqual(plogging.foundErrors(), True)
     plogging.reset()
     self.assertEqual(plogging.foundErrors(), False)
Example #6
0
    def test_direct(self):

        class DullHandler(logging.NullHandler):
            level = logging.DEBUG
            def emit(self, record):
                log_entry = self.format(record)

        plogging.addHandler(DullHandler())
        plogging.setLevel(logging.DEBUG)
        self.assertEqual(plogging.getEffectiveLevel(), logging.DEBUG)
        plogging.debug("hello world -- debug")
        plogging.info("hello world -- info")
        plogging.warning("hello world -- warning")
        plogging.error("hello world -- error")
        plogging.critical("hello world -- critical")
        self.assertEqual(plogging.foundErrors(), True)
        plogging.reset()
        self.assertEqual(plogging.foundErrors(), False)
Example #7
0
    def expand_labels(self, label):
        """
        Designates multiple nodes with a simple label

        :param label: the label to be expanded, e.g., ``server[1..2]_eu``
        :type label: ``str``

        :return: a list of names, e.g., ``['server1_eu', 'server2_eu']``
        :rtype: ``list`` of ``str``

        This function creates multiple names where applicable::

            >>>nodes.expand_labels('mongodb')
            ['mongodb']

            >>>nodes.expand_labels('mongodb[1..3]_eu')
            ['mongodb1_eu', 'mongodb2_eu', 'mongodb3_eu']

        """
        matches = re.match(r'(.*)\[([0-9]+)..([0-9]+)\](.*)', label)
        if matches is None:
            if re.match("^[0-9a-zA-Z]([0-9a-zA-Z\-]{0,61}[0-9a-zA-Z])?$",
                        label) is None:
                plogging.warning(
                    "Warning: '{}' is not a valid hostname".format(label))
            return [label]

        labels = []
        for index in range(int(matches.group(2)), int(matches.group(3)) + 1):

            label = matches.group(1) + str(index) + matches.group(4)
            if re.match("^[0-9a-zA-Z]([0-9a-zA-Z\-]{0,61}[0-9a-zA-Z])?$",
                        label) is None:
                plogging.warning(
                    "Warning: '{}' is not a valid hostname".format(label))

            labels.append(label)

        return labels
Example #8
0
    def focus(self):
        """
        Where are we plumbing?

        """

        self.power_on()
        plogging.info("Plumbing at '{}' {} ({})".format(
            self.location.id,
            self.location.name,
            self.location.country))

        blueprints = self.list_blueprints()
        if len(blueprints) < 1:
            plogging.warning("- no blueprint has been found")
        else:
            plogging.debug("- available blueprints: {}".format(
                "'"+"', '".join(blueprints)+"'"))

        basement = self.list_basement()
        if len(basement) > 0:
            plogging.debug("- basement: {}".format(
                "'"+"', '".join(basement)+"'"))
Example #9
0
    def focus(self):
        """
        Where are we plumbing?

        """

        self.power_on()
        plogging.info("Plumbing at '{}' {} ({})".format(
            self.location.id, self.location.name, self.location.country))

        blueprints = self.list_blueprints()
        if len(blueprints) < 1:
            plogging.warning("- no blueprint has been found")
        else:
            plogging.debug(
                "- available blueprints: {}".format("'" +
                                                    "', '".join(blueprints) +
                                                    "'"))

        basement = self.list_basement()
        if len(basement) > 0:
            plogging.debug(
                "- basement: {}".format("'" + "', '".join(basement) + "'"))
Example #10
0
    def expand_labels(self, label):
        """
        Designates multiple nodes with a simple label

        :param label: the label to be expanded, e.g., ``server[1..2]_eu``
        :type label: ``str``

        :return: a list of names, e.g., ``['server1_eu', 'server2_eu']``
        :rtype: ``list`` of ``str``

        This function creates multiple names where applicable::

            >>>nodes.expand_labels('mongodb')
            ['mongodb']

            >>>nodes.expand_labels('mongodb[1..3]_eu')
            ['mongodb1_eu', 'mongodb2_eu', 'mongodb3_eu']

        """
        matches = re.match(r'(.*)\[([0-9]+)..([0-9]+)\](.*)', label)
        if matches is None:
            if re.match("^[0-9a-zA-Z]([0-9a-zA-Z\-]{0,61}[0-9a-zA-Z])?$", label) is None:
                plogging.warning("Warning: '{}' is not a valid hostname"
                                 .format(label))
            return [label]

        labels = []
        for index in range(int(matches.group(2)), int(matches.group(3))+1):

            label = matches.group(1)+str(index)+matches.group(4)
            if re.match("^[0-9a-zA-Z]([0-9a-zA-Z\-]{0,61}[0-9a-zA-Z])?$", label) is None:
                plogging.warning("Warning: '{}' is not a valid hostname"
                                 .format(label))

            labels.append(label)

        return labels
Example #11
0
    def get_node(self, path):
        """
        Retrieves a node by name

        :param path: the name of the target node, or its location
        :type path: ``str`` or ``list``of ``str``

        :return: the target node, or None
        :rtype: :class:`libcloud.compute.base.Node`

        This function always make a real API call to get fresh state of the
        target node. Therefore, it can be used in loops where you monitor
        the evolution of the node during build or other change operation.

        This function searches firstly at the current facility. If the
        name is a complete path to a remote node, then plumbery looks
        there. If a different region is provided, then authentication is done
        against the related endpoint.

        For example if ``MyServer`` has been defined in a data centre in
        Europe::

            >>>infrastructure.get_ethernet('MyServer')
            >>>infrastructure.get_ethernet(['EU6', 'MyServer'])
            Looking for remote node 'EU6::MyServer'
            - found it
            >>>infrastructure.get_ethernet(['dd-eu', 'EU6', 'MyServer'])
            Looking for offshore node 'dd-eu::EU6::MyServer'
            - found it


        """

        if isinstance(path, str):
            path = path.split('::')

        node = None

        if len(path) == 2:  # force offshore lookup if needed
            target_region = self.facility.get_region(path[0])
            if target_region != self.facility.get_region():
                path.insert(0, target_region)

        if len(path) == 1:  # local name

            self.facility.power_on()

            for node in self.region.list_nodes():

                if node.extra['datacenterId'] != self.facility.get_location_id(
                ):
                    continue

                if node.name == path[0]:

                    self._enrich_node(node)
                    return node

        elif len(path) == 2:  # different location, same region

            self.facility.power_on()

            try:
                self.region.ex_get_location_by_id(path[0])
            except IndexError:
                plogging.warning("'{}' is unknown".format(path[0]))
                return None

            plogging.debug("Looking for remote node '{}'".format(
                '::'.join(path)))

            for node in self.region.list_nodes():

                if node.extra['datacenterId'] != path[0]:
                    continue

                if node.name == path[1]:

                    plogging.debug("- found it")

                    self._enrich_node(node)
                    return node

        elif len(path) == 3:  # other region

            offshore = self.plumbery.get_compute_driver(region=path[0])

            try:
                remoteLocation = offshore.ex_get_location_by_id(path[1])
            except IndexError:
                plogging.warning("'{}' is unknown".format(path[1]))
                return None

            plogging.debug("Looking for offshore node '{}'".format(
                '::'.join(path)))

            for node in offshore.list_nodes():

                if node.extra['datacenterId'] != path[1]:
                    continue

                if node.name == path[2]:

                    plogging.debug("- found it")

                    self._enrich_node(node, region=offshore)
                    return node

        return None
    def _apply_prepares(self, node, steps):
        """
        Does the actual job over SSH

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param steps: the various steps of the preparing
        :type steps: :class:`libcloud.compute.deployment.MultiStepDeployment`

        :return: ``True`` if everything went fine, ``False`` otherwise
        :rtype: ``bool``

        """

        if node is None or node.state != NodeState.RUNNING:
            plogging.warning("- skipped - node is not running")
            return False

        # select the address to use
        if len(node.public_ips) > 0:
            target_ip = node.public_ips[0]
        elif node.extra['ipv6']:
            target_ip = node.extra['ipv6']
        else:
            target_ip = node.private_ips[0]

        # guess location of user key
        path = os.path.expanduser('~/.ssh/id_rsa')

        # use libcloud to communicate with remote nodes
        session = SSHClient(hostname=target_ip,
                            port=22,
                            username=self.user,
                            password=self.secret,
                            key_files=path,
                            timeout=9)

        try:
            session.connect()
        except Exception as feedback:
            plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                node.name, target_ip))
            plogging.error(str(feedback))
            plogging.error("- failed")
            return False

        while True:
            try:
                if self.engine.safeMode:
                    plogging.info("- skipped - no ssh interaction in safe mode")

                else:
                    node = steps.run(node, session)

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                    node.name, target_ip))
                plogging.error(str(feedback))
                plogging.error("- failed")
                result = False

            else:
                result = True

            break

        try:
            session.close()
        except:
            pass

        return result
Example #13
0
    def _apply_prepares(self, node, steps):
        """
        Does the actual job over SSH

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param steps: the various steps of the preparing
        :type steps: ``list`` of ``dict``

        :return: ``True`` if everything went fine, ``False`` otherwise
        :rtype: ``bool``

        """

        if node is None or node.state != NodeState.RUNNING:
            plogging.warning("- skipped - node is not running")
            return False

        # select the address to use
        if len(node.public_ips) > 0:
            target_ip = node.public_ips[0]
        elif node.extra['ipv6']:
            target_ip = node.extra['ipv6']
        else:
            target_ip = node.private_ips[0]

        # use libcloud to communicate with remote nodes
        session = SSHClient(hostname=target_ip,
                            port=22,
                            username=self.user,
                            password=self.secret,
                            key_files=self.key_files,
                            timeout=10)

        repeats = 0
        while True:
            try:
                session.connect()
                break

            except Exception as feedback:
                repeats += 1
                if repeats > 5:
                    plogging.error("Error: can not connect to '{}'!".format(
                        target_ip))
                    plogging.error("- failed to connect")
                    return False

                plogging.debug(str(feedback))
                plogging.debug("- connection {} failed, retrying".format(repeats))
                time.sleep(10)
                continue

        while True:
            try:
                if self.engine.safeMode:
                    plogging.info("- skipped - no ssh interaction in safe mode")

                else:
                    for step in steps:
                        plogging.info('- {}'.format(step['description']))
                        step['genius'].run(node, session)

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                    node.name, target_ip))
                plogging.error(str(feedback))
                plogging.error("- failed")
                result = False

            else:
                result = True

            break

        try:
            session.close()
        except:
            pass

        return result
Example #14
0
    def _get_prepares(self, node, settings, container):
        """
        Defines the set of actions to be done on a node

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param settings: the fittings plan for this node
        :type settings: ``dict``

        :param container: the container of this node
        :type container: :class:`plumbery.PlumberyInfrastructure`

        :return: a list of actions to be performed, and related descriptions
        :rtype: a ``list`` of `{ 'description': ..., 'genius': ... }``

        """

        if not isinstance(settings, dict):
            return []

        environment = PlumberyNodeContext(node=node,
                                          container=container,
                                          context=self.facility)

        prepares = []

        for key_file in self.key_files:
            try:
                path = os.path.expanduser(key_file)

                with open(path) as stream:
                    key = stream.read()
                    stream.close()

                prepares.append({
                    'description': 'deploy SSH public key',
                    'genius': SSHKeyDeployment(key=key)})

            except IOError:
                plogging.warning("no ssh key in {}".format(key_file))

        if ('prepare' in settings
                and isinstance(settings['prepare'], list)
                and len(settings['prepare']) > 0):

            plogging.info('- using prepare commands')

            for script in settings['prepare']:

                tokens = script.split(' ')
                if len(tokens) == 1:
                    tokens.insert(0, 'run')

                if tokens[0] in ['run', 'run_raw']:  # send and run a script

                    script = tokens[1]
                    if len(tokens) > 2:
                        args = tokens[2:]
                    else:
                        args = []

                    plogging.debug("- {} {} {}".format(
                        tokens[0], script, ' '.join(args)))

                    try:
                        with open(script) as stream:
                            text = stream.read()

                            if(tokens[0] == 'run'
                                    and PlumberyText.could_expand(text)):

                                plogging.debug("- expanding script '{}'"
                                              .format(script))
                                text = PlumberyText.expand_string(
                                    text, environment)

                            if len(text) > 0:

                                plogging.info("- running '{}'"
                                                  .format(script))

                                prepares.append({
                                    'description': ' '.join(tokens),
                                    'genius': ScriptDeployment(
                                        script=text,
                                        args=args,
                                        name=script)})

                            else:
                                plogging.error("- script '{}' is empty"
                                              .format(script))

                    except IOError:
                        plogging.error("- unable to read script '{}'"
                                      .format(script))

                elif tokens[0] in ['put', 'put_raw']:  # send a file

                    file = tokens[1]
                    if len(tokens) > 2:
                        destination = tokens[2]
                    else:
                        destination = './'+file

                    plogging.debug("- {} {} {}".format(
                        tokens[0], file, destination))

                    try:
                        with open(file) as stream:
                            content = stream.read()

                            if(tokens[0] == 'put'
                                    and PlumberyText.could_expand(content)):

                                plogging.debug("- expanding file '{}'"
                                              .format(file))
                                content = PlumberyText.expand_string(
                                    content, environment)

                            plogging.info("- putting file '{}'"
                                              .format(file))
                            prepares.append({
                                'description': ' '.join(tokens),
                                'genius': FileContentDeployment(
                                    content=content,
                                    target=destination)})

                    except IOError:
                        plogging.error("- unable to read file '{}'"
                                      .format(file))

                else:  # echo a sensible message eventually

                    if tokens[0] == 'echo':
                        tokens.pop(0)
                    message = ' '.join(tokens)
                    message = PlumberyText.expand_string(
                        message, environment)
                    plogging.info("- {}".format(message))

        if ('cloud-config' in settings
                and isinstance(settings['cloud-config'], dict)
                and len(settings['cloud-config']) > 0):

            plogging.info('- using cloud-config')

            # mandatory, else cloud-init will not consider user-data
            plogging.debug('- preparing meta-data')
            meta_data = 'instance_id: dummy\n'

            destination = '/var/lib/cloud/seed/nocloud-net/meta-data'
            prepares.append({
                'description': 'put meta-data',
                'genius': FileContentDeployment(
                    content=meta_data,
                    target=destination)})

            plogging.debug('- preparing user-data')

            expanded = PlumberyText.expand_string(
                settings['cloud-config'], environment)

            user_data = '#cloud-config\n'+expanded
            plogging.debug(user_data)

            destination = '/var/lib/cloud/seed/nocloud-net/user-data'
            prepares.append({
                'description': 'put user-data',
                'genius': FileContentDeployment(
                    content=user_data,
                    target=destination)})

            plogging.debug('- preparing remote install of cloud-init')

            script = 'prepare.cloud-init.sh'
            try:
                path = os.path.dirname(__file__)+'/'+script
                with open(path) as stream:
                    text = stream.read()
                    if text:
                        prepares.append({
                            'description': 'run '+script,
                            'genius': ScriptDeployment(
                                script=text,
                                name=script)})

            except IOError:
                raise PlumberyException("Error: cannot read '{}'"
                                        .format(script))

            plogging.debug('- preparing reboot to trigger cloud-init')

            prepares.append({
                'description': 'reboot node',
                'genius': RebootDeployment(
                    container=container)})

        return prepares
Example #15
0
    def get_node(self, path):
        """
        Retrieves a node by name

        :param path: the name of the target node, or its location
        :type path: ``str`` or ``list``of ``str``

        :return: the target node, or None
        :rtype: :class:`libcloud.compute.base.Node`

        This function always make a real API call to get fresh state of the
        target node. Therefore, it can be used in loops where you monitor
        the evolution of the node during build or other change operation.

        This function searches firstly at the current facility. If the
        name is a complete path to a remote node, then plumbery looks
        there. If a different region is provided, then authentication is done
        against the related endpoint.

        For example if ``MyServer`` has been defined in a data centre in
        Europe::

            >>>infrastructure.get_ethernet('MyServer')
            >>>infrastructure.get_ethernet(['EU6', 'MyServer'])
            Looking for remote node 'EU6::MyServer'
            - found it
            >>>infrastructure.get_ethernet(['dd-eu', 'EU6', 'MyServer'])
            Looking for offshore node 'dd-eu::EU6::MyServer'
            - found it


        """

        if isinstance(path, str):
            path = path.split('::')

        node = None

        if len(path) == 2:  # force offshore lookup if needed
            target_region = self.facility.get_region(path[0])
            if target_region != self.facility.get_region():
                path.insert(0, target_region)

        if len(path) == 1:  # local name

            self.facility.power_on()

            for node in self.region.list_nodes():

                if node.extra['datacenterId'] != self.facility.get_location_id():
                    continue

                if node.name == path[0]:

                    self._enrich_node(node)
                    return node

        elif len(path) == 2:  # different location, same region

            self.facility.power_on()

            try:
                self.region.ex_get_location_by_id(path[0])
            except IndexError:
                plogging.warning("'{}' is unknown".format(path[0]))
                return None

            plogging.debug("Looking for remote node '{}'"
                          .format('::'.join(path)))

            for node in self.region.list_nodes():

                if node.extra['datacenterId'] != path[0]:
                    continue

                if node.name == path[1]:

                    plogging.debug("- found it")

                    self._enrich_node(node)
                    return node

        elif len(path) == 3:  # other region

            offshore = self.plumbery.get_compute_driver(region=path[0])

            try:
                remoteLocation = offshore.ex_get_location_by_id(path[1])
            except IndexError:
                plogging.warning("'{}' is unknown".format(path[1]))
                return None

            plogging.debug("Looking for offshore node '{}'"
                          .format('::'.join(path)))

            for node in offshore.list_nodes():

                if node.extra['datacenterId'] != path[1]:
                    continue

                if node.name == path[2]:

                    plogging.debug("- found it")

                    self._enrich_node(node, region=offshore)
                    return node

        return None
Example #16
0
    def _get_prepares(self, node, settings, container):
        """
        Defines the set of actions to be done on a node

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param settings: the fittings plan for this node
        :type settings: ``dict``

        :param container: the container of this node
        :type container: :class:`plumbery.PlumberyInfrastructure`

        :return: a list of actions to be performed, and related descriptions
        :rtype: a ``list`` of `{ 'description': ..., 'genius': ... }``

        """

        if not isinstance(settings, dict):
            return []

        environment = PlumberyNodeContext(node=node,
                                          container=container,
                                          context=self.facility)

        prepares = []

        for key_file in self.key_files:
            try:
                path = os.path.expanduser(key_file)

                with open(path) as stream:
                    key = stream.read()
                    stream.close()

                prepares.append({
                    'description': 'deploy SSH public key',
                    'genius': SSHKeyDeployment(key=key)
                })

            except IOError:
                plogging.warning("no ssh key in {}".format(key_file))

        if ('prepare' in settings and isinstance(settings['prepare'], list)
                and len(settings['prepare']) > 0):

            plogging.info('- using prepare commands')

            for script in settings['prepare']:

                tokens = script.split(' ')
                if len(tokens) == 1:
                    tokens.insert(0, 'run')

                if tokens[0] in ['run', 'run_raw']:  # send and run a script

                    script = tokens[1]
                    if len(tokens) > 2:
                        args = tokens[2:]
                    else:
                        args = []

                    plogging.debug("- {} {} {}".format(tokens[0], script,
                                                       ' '.join(args)))

                    try:
                        with open(script) as stream:
                            text = stream.read()

                            if (tokens[0] == 'run'
                                    and PlumberyText.could_expand(text)):

                                plogging.debug(
                                    "- expanding script '{}'".format(script))
                                text = PlumberyText.expand_string(
                                    text, environment)

                            if len(text) > 0:

                                plogging.info("- running '{}'".format(script))

                                prepares.append({
                                    'description':
                                    ' '.join(tokens),
                                    'genius':
                                    ScriptDeployment(script=text,
                                                     args=args,
                                                     name=script)
                                })

                            else:
                                plogging.error(
                                    "- script '{}' is empty".format(script))

                    except IOError:
                        plogging.error(
                            "- unable to read script '{}'".format(script))

                elif tokens[0] in ['put', 'put_raw']:  # send a file

                    file = tokens[1]
                    if len(tokens) > 2:
                        destination = tokens[2]
                    else:
                        destination = './' + file

                    plogging.debug("- {} {} {}".format(tokens[0], file,
                                                       destination))

                    try:
                        with open(file) as stream:
                            content = stream.read()

                            if (tokens[0] == 'put'
                                    and PlumberyText.could_expand(content)):

                                plogging.debug(
                                    "- expanding file '{}'".format(file))
                                content = PlumberyText.expand_string(
                                    content, environment)

                            plogging.info("- putting file '{}'".format(file))
                            prepares.append({
                                'description':
                                ' '.join(tokens),
                                'genius':
                                FileContentDeployment(content=content,
                                                      target=destination)
                            })

                    except IOError:
                        plogging.error(
                            "- unable to read file '{}'".format(file))

                else:  # echo a sensible message eventually

                    if tokens[0] == 'echo':
                        tokens.pop(0)
                    message = ' '.join(tokens)
                    message = PlumberyText.expand_string(message, environment)
                    plogging.info("- {}".format(message))

        if ('cloud-config' in settings
                and isinstance(settings['cloud-config'], dict)
                and len(settings['cloud-config']) > 0):

            plogging.info('- using cloud-config')

            # mandatory, else cloud-init will not consider user-data
            plogging.debug('- preparing meta-data')
            meta_data = 'instance_id: dummy\n'

            destination = '/var/lib/cloud/seed/nocloud-net/meta-data'
            prepares.append({
                'description':
                'put meta-data',
                'genius':
                FileContentDeployment(content=meta_data, target=destination)
            })

            plogging.debug('- preparing user-data')

            expanded = PlumberyText.expand_string(settings['cloud-config'],
                                                  environment)

            user_data = '#cloud-config\n' + expanded
            plogging.debug(user_data)

            destination = '/var/lib/cloud/seed/nocloud-net/user-data'
            prepares.append({
                'description':
                'put user-data',
                'genius':
                FileContentDeployment(content=user_data, target=destination)
            })

            plogging.debug('- preparing remote install of cloud-init')

            script = 'prepare.cloud-init.sh'
            try:
                path = os.path.dirname(__file__) + '/' + script
                with open(path) as stream:
                    text = stream.read()
                    if text:
                        prepares.append({
                            'description':
                            'run ' + script,
                            'genius':
                            ScriptDeployment(script=text, name=script)
                        })

            except IOError:
                raise PlumberyException(
                    "Error: cannot read '{}'".format(script))

            plogging.debug('- preparing reboot to trigger cloud-init')

            prepares.append({
                'description': 'reboot node',
                'genius': RebootDeployment(container=container)
            })

        return prepares
Example #17
0
    def _apply_prepares(self, node, steps):
        """
        Does the actual job over SSH

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param steps: the various steps of the preparing
        :type steps: ``list`` of ``dict``

        :return: ``True`` if everything went fine, ``False`` otherwise
        :rtype: ``bool``

        """

        if node is None or node.state != NodeState.RUNNING:
            plogging.warning("- skipped - node is not running")
            return False

        # select the address to use
        if len(node.public_ips) > 0:
            target_ip = node.public_ips[0]
        elif node.extra['ipv6']:
            target_ip = node.extra['ipv6']
        else:
            target_ip = node.private_ips[0]

        # use libcloud to communicate with remote nodes
        session = SSHClient(hostname=target_ip,
                            port=22,
                            username=self.user,
                            password=self.secret,
                            key_files=self.key_files,
                            timeout=10)

        repeats = 0
        while True:
            try:
                session.connect()
                break

            except Exception as feedback:
                repeats += 1
                if repeats > 5:
                    plogging.error(
                        "Error: can not connect to '{}'!".format(target_ip))
                    plogging.error("- failed to connect")
                    return False

                plogging.debug(str(feedback))
                plogging.debug(
                    "- connection {} failed, retrying".format(repeats))
                time.sleep(10)
                continue

        while True:
            try:
                if self.engine.safeMode:
                    plogging.info(
                        "- skipped - no ssh interaction in safe mode")

                else:
                    for step in steps:
                        plogging.info('- {}'.format(step['description']))
                        step['genius'].run(node, session)

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                    node.name, target_ip))
                plogging.error(str(feedback))
                plogging.error("- failed")
                result = False

            else:
                result = True

            break

        try:
            session.close()
        except:
            pass

        return result
Example #18
0
    def _apply_prepares(self, node, steps):
        """
        Does the actual job over SSH

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param steps: the various steps of the preparing
        :type steps: :class:`libcloud.compute.deployment.MultiStepDeployment`

        :return: ``True`` if everything went fine, ``False`` otherwise
        :rtype: ``bool``

        """

        if node is None or node.state != NodeState.RUNNING:
            plogging.warning("- skipped - node is not running")
            return False

        # select the address to use
        if len(node.public_ips) > 0:
            target_ip = node.public_ips[0]
        elif node.extra['ipv6']:
            target_ip = node.extra['ipv6']
        else:
            target_ip = node.private_ips[0]

        # guess location of user key
        path = os.path.expanduser('~/.ssh/id_rsa')

        # use libcloud to communicate with remote nodes
        session = SSHClient(hostname=target_ip,
                            port=22,
                            username=self.user,
                            password=self.secret,
                            key_files=path,
                            timeout=9)

        try:
            session.connect()
        except Exception as feedback:
            plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                node.name, target_ip))
            plogging.error(str(feedback))
            plogging.error("- failed")
            return False

        while True:
            try:
                if self.engine.safeMode:
                    plogging.info(
                        "- skipped - no ssh interaction in safe mode")

                else:
                    node = steps.run(node, session)

            except Exception as feedback:
                if 'RESOURCE_BUSY' in str(feedback):
                    time.sleep(10)
                    continue

                plogging.error("Error: unable to prepare '{}' at '{}'!".format(
                    node.name, target_ip))
                plogging.error(str(feedback))
                plogging.error("- failed")
                result = False

            else:
                result = True

            break

        try:
            session.close()
        except:
            pass

        return result
Example #19
0
    def configure(self, node, settings):
        """
        prepares a node

        :param node: the node to be polished
        :type node: :class:`libcloud.compute.base.Node`

        :param settings: the fittings plan for this node
        :type settings: ``dict``

        :param container: the container of this node
        :type container: :class:`plumbery.PlumberyInfrastructure`

        """
        if self._element_name_ in settings:
            plogging.info("preparing node '{}'".format(settings['name']))
            if node is None:
                plogging.info("- not found")
                return

            timeout = 300
            tick = 6
            while node.extra['status'].action == 'START_SERVER':
                time.sleep(tick)
                node = self.nodes.get_node(node.name)
                timeout -= tick
                if timeout < 0:
                    break

                if node.state != NodeState.RUNNING:
                    plogging.info("- skipped - node is not running")
                    return

            ipv6 = node.extra['ipv6']
            ip = node.private_ips[0]
            if ipv6 is None:
                plogging.error('No ipv6 address for node, cannot configure')
                return

            # Check to see if WinRM works..
            try:
                self._try_winrm(node)
            except winrm.exceptions.InvalidCredentialsError:
                plogging.warn('initial login to %s failed, trying to setup winrm remotely',
                             ip)
                self._setup_winrm(node)
                self._try_winrm(node)
            except requests.exceptions.ConnectionError:
                plogging.warn('initial connection to %s failed, trying to setup winrm remotely',
                             ip)
                self._setup_winrm(node)
                self._try_winrm(node)

            # OK, we're all ready. Let's look at the node config and start commands
            cmds = []
            hostname = settings[self._element_name_].get('hostname', None)
            if hostname is not None and isinstance(hostname, str):
                cmds.append(('powershell.exe', ['Rename-Computer', '-NewName', hostname]))

            extra_cmds = settings[self._element_name_].get('cmds', [])
            for command in extra_cmds:
                command = command.rstrip()
                command_parts = command.split(' ')
                cmds.append((command_parts[0], command_parts[1:]))

            out, err = self._winrm_commands(node, cmds)
            plogging.info(out)
            plogging.warning(err)

            plogging.debug('locking down winrm')
            self._lockdown_winrm(node)
        else:
            return False