Added password support for SSH and Ansible 71/50971/2
authorspisarski <s.pisarski@cablelabs.com>
Tue, 23 Jan 2018 02:27:31 +0000 (19:27 -0700)
committerspisarski <s.pisarski@cablelabs.com>
Tue, 23 Jan 2018 07:33:39 +0000 (00:33 -0700)
Additional protections when initializing network resources
Enhanced playbook runner variable support

Change-Id: Id897c4ece1de706afbc52d0a034ca3bfb68fdce3
Signed-off-by: spisarski <s.pisarski@cablelabs.com>
requirements.txt
snaps/openstack/create_instance.py
snaps/openstack/create_network.py
snaps/openstack/create_router.py
snaps/openstack/utils/launch_utils.py
snaps/openstack/utils/nova_utils.py
snaps/playbook_runner.py
snaps/provisioning/ansible_utils.py
snaps/provisioning/tests/ansible_utils_tests.py

index 137159f..f992fb4 100644 (file)
@@ -1,7 +1,7 @@
 # The order of packages is significant, because pip processes them in the order
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
-python-novaclient>=9.0.0 # Apache-2.0
+python-novaclient>=9.0.0,<=10 # Apache-2.0
 python-neutronclient>=6.3.0 # Apache-2.0
 python-keystoneclient>=3.8.0 # Apache-2.0
 python-glanceclient>=2.8.0 # Apache-2.0
index d91e360..631ac6b 100644 (file)
@@ -380,7 +380,7 @@ class OpenStackVmInstance(OpenStackComputeObject):
                     logger.error('Cannot add floating IP [%s]', bre)
                     raise
                 except Exception as e:
-                    logger.debug(
+                    logger.warn(
                         'Retry adding floating IP to instance. Last attempt '
                         'failed with - %s', e)
                     time.sleep(poll_interval)
@@ -509,8 +509,9 @@ class OpenStackVmInstance(OpenStackComputeObject):
         """
         return ansible_utils.apply_playbook(
             pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
-            self.get_image_user(), self.keypair_settings.private_filepath,
-            variables, self._os_creds.proxy_settings)
+            self.get_image_user(),
+            ssh_priv_key_file_path=self.keypair_settings.private_filepath,
+            variables=variables, proxy_setting=self._os_creds.proxy_settings)
 
     def get_image_user(self):
         """
@@ -619,7 +620,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
             status)
         return status == expected_status_code
 
-    def vm_ssh_active(self, block=False, poll_interval=POLL_INTERVAL):
+    def vm_ssh_active(self, user_override=None, password=None, block=False,
+                      timeout=None, poll_interval=POLL_INTERVAL):
         """
         Returns true when the VM can be accessed via SSH
         :param block: When true, thread will block until active or timeout
@@ -630,7 +632,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
         # sleep and wait for VM status change
         logger.info('Checking if VM is active')
 
-        timeout = self.instance_settings.ssh_connect_timeout
+        if not timeout:
+            timeout = self.instance_settings.ssh_connect_timeout
 
         if self.vm_active(block=True):
             if block:
@@ -639,7 +642,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
                 start = time.time() - timeout
 
             while timeout > time.time() - start:
-                status = self.__ssh_active()
+                status = self.__ssh_active(
+                    user_override=user_override, password=password)
                 if status:
                     logger.info('SSH is active for VM instance')
                     return True
@@ -653,13 +657,14 @@ class OpenStackVmInstance(OpenStackComputeObject):
         logger.error('Timeout attempting to connect with VM via SSH')
         return False
 
-    def __ssh_active(self):
+    def __ssh_active(self, user_override=None, password=None):
         """
         Returns True when can create a SSH session else False
         :return: T/F
         """
         if len(self.__floating_ip_dict) > 0:
-            ssh = self.ssh_client()
+            ssh = self.ssh_client(
+                user_override=user_override, password=password)
             if ssh:
                 ssh.close()
                 return True
@@ -727,19 +732,32 @@ class OpenStackVmInstance(OpenStackComputeObject):
         else:
             return self.__get_first_provisioning_floating_ip()
 
-    def ssh_client(self, fip_name=None):
+    def ssh_client(self, fip_name=None, user_override=None, password=None):
         """
         Returns an SSH client using the name or the first known floating IP if
         exists, else None
         :param fip_name: the name of the floating IP to return
+        :param user_override: the username to use instead of the default
+        :param password: the password to use instead of the private key
         :return: the SSH client or None
         """
         fip = self.get_floating_ip(fip_name)
+
+        ansible_user = self.get_image_user()
+        if user_override:
+            ansible_user = user_override
+
+        if password:
+            private_key = None
+        else:
+            private_key = self.keypair_settings.private_filepath
+
         if fip:
             return ansible_utils.ssh_client(
                 self.__get_first_provisioning_floating_ip().ip,
-                self.get_image_user(),
-                self.keypair_settings.private_filepath,
+                ansible_user,
+                private_key_filepath=private_key,
+                password=password,
                 proxy_settings=self._os_creds.proxy_settings)
         else:
             FloatingIPAllocationError(
index c9c58e8..984eedd 100644 (file)
@@ -15,7 +15,7 @@
 import logging
 
 import enum
-from neutronclient.common.exceptions import NetworkNotFoundClient
+from neutronclient.common.exceptions import NetworkNotFoundClient, Unauthorized
 
 from snaps.config.network import NetworkConfig, SubnetConfig, PortConfig
 from snaps.openstack.openstack_creator import OpenStackNetworkObject
@@ -51,9 +51,14 @@ class OpenStackNetwork(OpenStackNetworkObject):
         """
         super(self.__class__, self).initialize()
 
-        self.__network = neutron_utils.get_network(
-            self._neutron, network_settings=self.network_settings,
-            project_id=self.network_settings.get_project_id(self._os_creds))
+        try:
+            self.__network = neutron_utils.get_network(
+                self._neutron, network_settings=self.network_settings,
+                project_id=self.network_settings.get_project_id(
+                    self._os_creds))
+        except Unauthorized as e:
+            logger.warn('Unable to lookup network with name %s - %s',
+                        self.network_settings.name, e)
 
         return self.__network
 
index 4f95c3b..c9ccdd6 100644 (file)
@@ -14,7 +14,7 @@
 # limitations under the License.
 import logging
 
-from neutronclient.common.exceptions import NotFound
+from neutronclient.common.exceptions import NotFound, Unauthorized
 
 from snaps.config.router import RouterConfig
 from snaps.openstack.openstack_creator import OpenStackNetworkObject
@@ -61,8 +61,12 @@ class OpenStackRouter(OpenStackNetworkObject):
         """
         super(self.__class__, self).initialize()
 
-        self.__router = neutron_utils.get_router(
-            self._neutron, router_settings=self.router_settings)
+        try:
+            self.__router = neutron_utils.get_router(
+                self._neutron, router_settings=self.router_settings)
+        except Unauthorized as e:
+            logger.warn('Unable to lookup router with name %s - %s',
+                        self.router_settings.name, e)
 
         if self.__router:
             for internal_subnet_name in self.router_settings.internal_subnets:
index e10cf48..05d4cb5 100644 (file)
@@ -462,7 +462,7 @@ def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict,
 
             retval = ansible_utils.apply_playbook(
                 ansible_config['playbook_location'], floating_ips, remote_user,
-                private_key_filepath,
+                ssh_priv_key_file_path=private_key_filepath,
                 variables=variables,
                 proxy_setting=proxy_settings)
             if retval != 0:
index e15484c..279e2ec 100644 (file)
@@ -69,8 +69,11 @@ def create_server(nova, neutron, glance, instance_config, image_config,
     ports = list()
 
     for port_setting in instance_config.port_settings:
-        ports.append(neutron_utils.get_port(
-            neutron, port_settings=port_setting))
+        port = neutron_utils.get_port(neutron, port_settings=port_setting)
+        if port:
+            ports.append(port)
+        else:
+            raise Exception('Cannot find port named - ' + port_setting.name)
     nics = []
     for port in ports:
         kv = dict()
index 87321f5..03b7006 100644 (file)
 import argparse
 import ast
 import logging
+import os
 
 import re
 
+import yaml
+from jinja2 import Environment, FileSystemLoader
+
 from snaps.openstack.os_credentials import ProxySettings
 from snaps.provisioning import ansible_utils
 
@@ -41,20 +45,32 @@ def main(parsed_args):
                                        ssh_proxy_cmd=parsed_args.ssh_proxy_cmd)
 
     # Ensure can get an SSH client
-    ssh = ansible_utils.ssh_client(parsed_args.ip_addr, parsed_args.host_user,
-                                   parsed_args.priv_key, proxy_settings)
+    ssh = ansible_utils.ssh_client(
+        parsed_args.ip_addr, parsed_args.host_user,
+        private_key_filepath=parsed_args.priv_key,
+        proxy_settings=proxy_settings)
     if ssh:
         ssh.close()
 
-    vars = dict()
-    if args.vars:
-        vars = ast.literal_eval(args.vars)
-        if not isinstance(vars, dict):
-            vars = dict()
+    env = Environment(loader=FileSystemLoader(
+        searchpath=os.path.dirname(parsed_args.env_file)))
+    template = env.get_template(os.path.basename(parsed_args.env_file))
+
+    env_dict = dict()
+    if parsed_args.vars:
+        env_dict = ast.literal_eval(parsed_args.vars)
+
+    output = template.render(**env_dict)
+
+    variables = yaml.load(output)
+
+    if not variables.get('env_file'):
+        variables['env_file'] = parsed_args.env_file
 
     retval = ansible_utils.apply_playbook(
         parsed_args.playbook, [parsed_args.ip_addr], parsed_args.host_user,
-        parsed_args.priv_key, variables=vars,
+        ssh_priv_key_file_path=parsed_args.priv_key,
+        password=parsed_args.password, variables=variables,
         proxy_setting=proxy_settings)
     exit(retval)
 
@@ -63,19 +79,26 @@ if __name__ == '__main__':
     parser = argparse.ArgumentParser()
     parser.add_argument('-a', '--ip-addr', dest='ip_addr', required=True,
                         help='The Host IP Address')
-    parser.add_argument('-k', '--priv-key', dest='priv_key', required=True,
-                        help='The location of the private key file')
     parser.add_argument('-u', '--host-user', dest='host_user', required=True,
                         help='Host user account')
+    parser.add_argument('-k', '--priv-key', dest='priv_key', required=False,
+                        help='The location of the private key file')
+    parser.add_argument('-pw', '--password', dest='password', required=False,
+                        help='The host-user password')
     parser.add_argument('-b', '--playbook', dest='playbook', required=True,
                         help='Playbook Location')
     parser.add_argument('-p', '--http-proxy', dest='http_proxy',
                         required=False, help='<host>:<port>')
     parser.add_argument('-s', '--ssh-proxy-cmd', dest='ssh_proxy_cmd',
                         required=False)
+    parser.add_argument('-e', '--env-file', dest='env_file',
+                        help='Yaml file containing playbook substitution vals',
+                        required=False)
     parser.add_argument('-v', '--vars', dest='vars',
+                        help='String renditon of a dict to pass into '
+                             'playbook for additional subtitution values not '
+                             'found in env_file',
                         required=False)
     args = parser.parse_args()
 
     main(args)
-
index 63f26e1..83fe449 100644 (file)
@@ -32,8 +32,9 @@ __author__ = 'spisarski'
 logger = logging.getLogger('ansible_utils')
 
 
-def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
-                   variables=None, proxy_setting=None):
+def apply_playbook(playbook_path, hosts_inv, host_user,
+                   ssh_priv_key_file_path=None, password=None, variables=None,
+                   proxy_setting=None):
     """
     Executes an Ansible playbook to the given host
     :param playbook_path: the (relative) path to the Ansible playbook
@@ -41,7 +42,10 @@ def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
                       Ansible playbook
     :param host_user: A user for the host instances (must be a password-less
                       sudo user if playbook has "sudo: yes"
-    :param ssh_priv_key_file_path: the file location of the ssh key
+    :param ssh_priv_key_file_path: the file location of the ssh key. Required
+                                   if password is None
+    :param password: the file location of the ssh key. Required if
+                     ssh_priv_key_file_path is None
     :param variables: a dictionary containing any substitution variables needed
                       by the Jinga 2 templates
     :param proxy_setting: instance of os_credentials.ProxySettings class
@@ -50,10 +54,20 @@ def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
     if not os.path.isfile(playbook_path):
         raise AnsibleException('Requested playbook not found - ' + playbook_path)
 
-    pk_file_path = os.path.expanduser(ssh_priv_key_file_path)
-    if not os.path.isfile(pk_file_path):
-        raise AnsibleException('Requested private SSH key not found - ' +
-                        pk_file_path)
+    pk_file_path = None
+    if ssh_priv_key_file_path:
+        pk_file_path = os.path.expanduser(ssh_priv_key_file_path)
+        if not password:
+            if not os.path.isfile(pk_file_path):
+                raise AnsibleException('Requested private SSH key not found - ' +
+                                pk_file_path)
+
+    if not ssh_priv_key_file_path and not password:
+        raise AnsibleException('Invalid credentials, no priv key or password')
+
+    passwords = None
+    if password:
+        passwords = {'conn_pass': password, 'become_pass': password}
 
     import ansible.constants
     ansible.constants.HOST_KEY_CHECKING = False
@@ -93,18 +107,20 @@ def apply_playbook(playbook_path, hosts_inv, host_user, ssh_priv_key_file_path,
         variable_manager=variable_manager,
         loader=loader,
         options=ansible_opts,
-        passwords=None)
+        passwords=passwords)
 
     logger.debug('Executing Ansible Playbook - ' + playbook_path)
     return executor.run()
 
 
-def ssh_client(ip, user, private_key_filepath, proxy_settings=None):
+def ssh_client(ip, user, private_key_filepath=None, password=None,
+               proxy_settings=None):
     """
     Retrieves and attemts an SSH connection
     :param ip: the IP of the host to connect
     :param user: the user with which to connect
-    :param private_key_filepath: the path to the private key file
+    :param private_key_filepath: when None, password is required
+    :param password: when None, private_key_filepath is required
     :param proxy_settings: instance of os_credentials.ProxySettings class
                            (optional)
     :return: the SSH client if can connect else false
@@ -120,9 +136,13 @@ def ssh_client(ip, user, private_key_filepath, proxy_settings=None):
             proxy_cmd_str = proxy_cmd_str.replace("%p", '22')
             proxy_cmd = paramiko.ProxyCommand(proxy_cmd_str)
 
-        pk_abs_path = os.path.expanduser(private_key_filepath)
-        ssh.connect(ip, username=user, key_filename=pk_abs_path,
-                    sock=proxy_cmd)
+        pk_abs_path = None
+        if not password and private_key_filepath:
+            pk_abs_path = os.path.expanduser(private_key_filepath)
+
+        ssh.connect(
+            ip, username=user, key_filename=pk_abs_path, password=password,
+            sock=proxy_cmd)
         return ssh
     except Exception as e:
         logger.warning('Unable to connect via SSH with message - ' + str(e))
index 7600002..851dd64 100644 (file)
@@ -266,8 +266,9 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
         retval = self.inst_creator.apply_ansible_playbook(relative_pb_path)
         self.assertEqual(0, retval)
 
-        ssh = ansible_utils.ssh_client(ip, user, priv_key,
-                                       self.os_creds.proxy_settings)
+        ssh = ansible_utils.ssh_client(
+            ip, user, private_key_filepath=priv_key,
+            proxy_settings=self.os_creds.proxy_settings)
         self.assertIsNotNone(ssh)
         scp = None
         try:
@@ -329,13 +330,13 @@ class AnsibleProvisioningTests(OSIntegrationTestCase):
         relative_pb_path = pkg_resources.resource_filename(
             'snaps.provisioning.tests.playbooks',
             'template_playbook.yml')
-        retval = self.inst_creator.apply_ansible_playbook(relative_pb_path,
-                                                          variables={
-                                                              'name': 'Foo'})
+        retval = self.inst_creator.apply_ansible_playbook(
+            relative_pb_path, variables={'name': 'Foo'})
         self.assertEqual(0, retval)
 
-        ssh = ansible_utils.ssh_client(ip, user, priv_key,
-                                       self.os_creds.proxy_settings)
+        ssh = ansible_utils.ssh_client(
+            ip, user, private_key_filepath=priv_key,
+            proxy_settings=self.os_creds.proxy_settings)
         self.assertIsNotNone(ssh)
         scp = None