Beispiel #1
0
    def _verify_metadata_file(self,
                              app_path,
                              app_name,
                              app_version,
                              upgrade_from_release=None):
        try:
            name, version, patches = cutils.find_metadata_file(
                app_path,
                constants.APP_METADATA_FILE,
                upgrade_from_release=upgrade_from_release)
        except exception.SysinvException as e:
            raise exception.SysinvException(
                _("metadata validation failed. {}".format(e)))

        if not name:
            name = app_name
        if not version:
            version = app_version

        if (not name or not version
                or name.startswith(constants.APP_VERSION_PLACEHOLDER)
                or version.startswith(constants.APP_VERSION_PLACEHOLDER)):
            raise exception.SysinvException(
                _("application name or/and version is/are not included "
                  "in the tar file. Please specify the application name "
                  "via --app-name or/and version via --app-version."))

        if patches:
            try:
                self._check_patching_operation()
            except exception.SysinvException as e:
                raise exception.SysinvException(
                    _("{}. Please upload after the patching operation "
                      "is completed.".format(e)))
            except Exception as e:
                raise exception.SysinvException(
                    _("{}. Communication Error with patching subsytem. "
                      "Preventing application upload.".format(e)))

            applied, missing_patches = \
                self._check_required_patches_are_applied(patches)
            if not applied:
                raise exception.SysinvException(
                    _("the required patch(es) ({}) for application {} ({}) "
                      "must be applied".format(', '.join(missing_patches),
                                               name, version)))

            LOG.info("The required patch(es) for application {} ({}) "
                     "has/have applied.".format(name, version))
        else:
            LOG.info("No patch required for application {} ({})."
                     "".format(name, version))

        return name, version, patches
Beispiel #2
0
def reset_device_n3000(pci_addr):
    # Reset the N3000 FPGA at the specified PCI address.
    try:
        # Build up the command to perform the reset.
        # Note the hack to work around OPAE tool locale issues
        cmd = ("docker run -t --privileged -e LC_ALL=en_US.UTF-8 "
               "-e LANG=en_US.UTF-8 " + OPAE_IMG +
               " rsu bmcimg " + pci_addr)

        # Issue the command to perform the firmware update.
        subprocess.check_output(shlex.split(cmd),
                                         stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as exc:
        # "docker run" return code will be:
        #    125 if the error is with Docker daemon itself
        #    126 if the contained command cannot be invoked
        #    127 if the contained command cannot be found
        #    Exit code of contained command otherwise
        msg = ("Failed to reset device %s, "
               "return code is %d, command output: %s." %
               (pci_addr, exc.returncode,
                exc.output.decode('utf-8')))
        LOG.error(msg)
        LOG.error("Check for intel-max10 kernel logs.")
        raise exception.SysinvException(msg)
Beispiel #3
0
    def _get_host_upgrade_config(self, host):
        config = {}
        try:
            upgrade = self.dbapi.software_upgrade_get_one()
        except exception.NotFound:
            return config

        upgrade_states = [constants.UPGRADE_ACTIVATING,
                          constants.UPGRADE_ACTIVATION_FAILED,
                          constants.UPGRADE_ACTIVATION_COMPLETE,
                          constants.UPGRADE_COMPLETED]
        # we don't need compatibility mode after we activate
        if upgrade.state in upgrade_states:
            return config

        upgrade_load_id = upgrade.to_load

        host_upgrade = self.dbapi.host_upgrade_get_by_host(host['id'])
        if host_upgrade.target_load == upgrade_load_id:
            from_load = self.dbapi.load_get(upgrade.from_load)
            sw_version = from_load.software_version
            nova_level = NOVA_UPGRADE_LEVELS.get(sw_version)

            if not nova_level:
                raise exception.SysinvException(
                    ("No matching upgrade level found for version %s")
                    % sw_version)

            config.update({
                  'nova::upgrade_level_compute': nova_level
            })

        return config
Beispiel #4
0
    def _check_required_patches_are_applied(self, patches=None):
        """Validates that each patch provided is applied on the system"""
        if patches is None:
            patches = []
        try:
            system = self._dbapi.isystem_get_one()
            response = patch_api.patch_query(
                token=None,
                timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
                region_name=system.region_name)
        except Exception as e:
            LOG.error(e)
            raise exception.SysinvException(
                _("Error while querying sw-patch-controller for the "
                  "state of the patch(es)."))
        query_patches = response['pd']
        applied_patches = []
        for patch_key in query_patches:
            patch = query_patches[patch_key]
            patchstate = patch.get('patchstate', None)
            if patchstate == patch_constants.APPLIED or \
                    patchstate == patch_constants.COMMITTED:
                applied_patches.append(patch_key)

        missing_patches = []
        for required_patch in patches:
            if required_patch not in applied_patches:
                missing_patches.append(required_patch)

        success = not missing_patches
        return success, missing_patches
Beispiel #5
0
    def reset_fernet_keys(self):
        try:
            if os.path.isdir(self.key_repository):
                LOG.info("Remove fernet repo")
                shutil.rmtree(self.key_repository)
        except OSError as e:
            LOG.exception(e)

        with open(os.devnull, "w") as fnull:
            try:
                LOG.info("Re-setup fernet repo")
                subprocess.check_call(
                    [
                        '/usr/bin/keystone-manage',  # pylint: disable=not-callable
                        'fernet_setup',
                        '--keystone-user',
                        KEYSTONE_USER,
                        '--keystone-group',
                        KEYSTONE_GROUP
                    ],
                    stdout=fnull,
                    stderr=fnull)
            except subprocess.CalledProcessError as e:
                msg = _("Failed to setup fernet keys: %s") % e.message
                LOG.exception(msg)
                raise exception.SysinvException(msg)
Beispiel #6
0
def write_device_image_n3000(filename, pci_addr):
    # Write the firmware image to the FPGA at the specified PCI address.
    # We're assuming that the image update tools will catch the scenario
    # where the image is not compatible with the device.
    try:
        # Build up the command to perform the firmware update.
        # Note the hack to work around OPAE tool locale issues
        cmd = ("docker run -t --privileged -e LC_ALL=en_US.UTF-8 "
               "-e LANG=en_US.UTF-8 -v " + DEVICE_IMAGE_CACHE_DIR +
               ":" + "/mnt/images " + OPAE_IMG +
               " fpgasupdate -y --log-level debug /mnt/images/" +
               filename + " " + pci_addr)

        # Issue the command to perform the firmware update.
        subprocess.check_output(shlex.split(cmd),
                                         stderr=subprocess.STDOUT)
        # TODO: switch to subprocess.Popen, parse the output and send
        #       progress updates.
    except subprocess.CalledProcessError as exc:
        # Check the return code, send completion info to sysinv-conductor.
        # "docker run" return code will be:
        #    125 if the error is with Docker daemon itself
        #    126 if the contained command cannot be invoked
        #    127 if the contained command cannot be found
        #    Exit code of contained command otherwise
        msg = ("Failed to update device image %s for device %s, "
               "return code is %d, command output: %s." %
               (filename, pci_addr, exc.returncode,
                exc.output.decode('utf-8')))
        LOG.error(msg)
        LOG.error("Check for intel-max10 kernel logs.")
        raise exception.SysinvException(msg)
Beispiel #7
0
    def cinder_prepare_db_for_volume_restore(self, context):
        """
        Make sure that Cinder's database is in the state required to restore all
        volumes.

        Instruct cinder to delete all of its volume snapshots and set all of its
        volume to the 'error' state.
        """
        LOG.debug("Prepare Cinder DB for volume Restore")
        try:
            # mark all volumes as 'error' state
            LOG.debug("Resetting all volumes to error state")
            all_tenant_volumes = self._get_cinderclient().volumes.list(
                search_opts={'all_tenants': 1})

            for vol in all_tenant_volumes:
                vol.reset_state('error')

            # delete all volume snapshots
            LOG.debug("Deleting all volume snapshots")
            all_tenant_snapshots = self._get_cinderclient(
            ).volume_snapshots.list(search_opts={'all_tenants': 1})

            for snap in all_tenant_snapshots:
                snap.delete()
        except Exception as e:
            LOG.exception("Cinder DB updates failed" % e)
            # Cinder cleanup is not critical, PV was already removed
            raise exception.SysinvException(
                _("Automated Cinder DB updates failed. Please manually set "
                  "all volumes to 'error' state and delete all volume "
                  "snapshots before restoring volumes."))
        LOG.debug("Cinder DB ready for volume Restore")
Beispiel #8
0
    def _get_kubernetes_join_cmd(self, host):
        # The token expires after 24 hours and is needed for a reinstall.
        # The puppet manifest handles the case where the node already exists.
        try:
            join_cmd_additions = ''
            if host.personality == constants.CONTROLLER:
                # Upload the certificates used during kubeadm join
                # The cert key will be printed in the last line of the output

                # We will create a temp file with the kubeadm config
                # We need this because the kubeadm config could have changed
                # since bootstrap. Reading the kubeadm config each time
                # it is needed ensures we are not using stale data

                fd, temp_kubeadm_config_view = tempfile.mkstemp(
                    dir='/tmp', suffix='.yaml')
                with os.fdopen(fd, 'w') as f:
                    cmd = ['kubeadm', KUBECONFIG, 'config', 'view']
                    subprocess.check_call(cmd, stdout=f)  # pylint: disable=not-callable

                # We will use a custom key to encrypt kubeadm certificates
                # to make sure all hosts decrypt using the same key

                key = str(keyring.get_password(CERTIFICATE_KEY_SERVICE,
                        CERTIFICATE_KEY_USER))
                with open(temp_kubeadm_config_view, "a") as f:
                    f.write("---\r\napiVersion: kubeadm.k8s.io/v1beta2\r\n"
                            "kind: InitConfiguration\r\ncertificateKey: "
                            "{}".format(key))

                cmd = ['kubeadm', 'init', 'phase', 'upload-certs',
                       '--upload-certs', '--config',
                       temp_kubeadm_config_view]

                subprocess.check_call(cmd)  # pylint: disable=not-callable
                join_cmd_additions = \
                    " --control-plane --certificate-key %s" % key
                os.unlink(temp_kubeadm_config_view)

                # Configure the IP address of the API Server for the controller host.
                # If not set the default network interface will be used, which does not
                # ensure it will be the Cluster IP address of this host.
                host_cluster_ip = self._get_host_cluster_address(host)
                join_cmd_additions += \
                    " --apiserver-advertise-address %s" % host_cluster_ip

            cmd = ['kubeadm', KUBECONFIG, 'token', 'create', '--print-join-command',
                   '--description', 'Bootstrap token for %s' % host.hostname]
            join_cmd = subprocess.check_output(cmd)  # pylint: disable=not-callable
            join_cmd_additions += \
                " --cri-socket /var/run/containerd/containerd.sock"
            join_cmd = join_cmd.strip() + join_cmd_additions
            LOG.info('get_kubernetes_join_cmd join_cmd=%s' % join_cmd)
        except Exception:
            LOG.exception("Exception generating bootstrap token")
            raise exception.SysinvException(
                'Failed to generate bootstrap token')

        return join_cmd
Beispiel #9
0
 def _extract_helm_charts(self, app_path, demote_user=False):
     charts_dir = os.path.join(app_path, 'charts')
     if os.path.isdir(charts_dir):
         tar_filelist = cutils.get_files_matching(app_path, '.tgz')
         if len(os.listdir(charts_dir)) == 0:
             raise exception.SysinvException(
                 _("tar file contains no Helm charts."))
         if not tar_filelist:
             raise exception.SysinvException(
                 _("tar file contains no Helm charts of "
                   "expected file extension (.tgz)."))
         for p, f in tar_filelist:
             if not cutils.extract_tarfile(p, os.path.join(p, f),
                                           demote_user):
                 raise exception.SysinvException(
                     _("failed to extract tar file {}.".format(
                         os.path.basename(f))))
Beispiel #10
0
    def _find_manifest_file(self, app_path):
        # It is expected that there is only one manifest file
        # per application and the file exists at top level of
        # the application path.
        mfiles = cutils.find_manifest_file(app_path)

        if mfiles is None:
            raise exception.SysinvException(_("manifest file is corrupted."))

        if mfiles:
            if len(mfiles) == 1:
                return mfiles[0]
            else:
                raise exception.SysinvException(
                    _("Application-upload rejected: tar file contains more "
                      "than one manifest file."))
        else:
            raise exception.SysinvException(
                _("Application-upload rejected: manifest file is missing."))
Beispiel #11
0
def ensure_device_image_cache_exists():
    # Make sure the image cache directory exists, create it if needed.
    try:
        os.mkdir(DEVICE_IMAGE_CACHE_DIR, 0o755)
    except OSError as exc:
        if exc.errno != errno.EEXIST:
            msg = ("Unable to create device image cache directory %s!"
                   % DEVICE_IMAGE_CACHE_DIR)
            LOG.exception(msg)
            raise exception.SysinvException(msg)
Beispiel #12
0
def fetch_device_image(filename):
    # Pull the image from the controller.
    url = "http://controller:8080/device_images/" + filename
    local_path = DEVICE_IMAGE_CACHE_DIR + "/" + filename
    try:
        imagefile, headers = urllib.urlretrieve(url, local_path)
    except IOError:
        msg = ("Unable to retrieve device image from %s!" % url)
        LOG.exception(msg)
        raise exception.SysinvException(msg)
    return local_path
Beispiel #13
0
def fetch_device_image_local(filename):
    # This is a hack since we only support AIO for now.  Just copy the device
    # image file into the well-known device image cache directory.
    local_path = DEVICE_IMAGE_CACHE_DIR + "/" + filename
    image_file_path = os.path.join(dconstants.DEVICE_IMAGE_PATH, filename)
    try:
        shutil.copyfile(image_file_path, local_path)
    except (shutil.Error, IOError):
        msg = ("Unable to retrieve device image from %s!" % image_file_path)
        LOG.exception(msg)
        raise exception.SysinvException(msg)
    return local_path
Beispiel #14
0
def db_sync(version=None):
    if version is not None:
        try:
            version = int(version)
        except ValueError:
            raise exception.SysinvException(_("version should be an integer"))

    current_version = db_version()
    repository = _find_migrate_repo()
    if version is None or version > current_version:
        return versioning_api.upgrade(get_engine(), repository, version)
    else:
        return versioning_api.downgrade(get_engine(), repository, version)
Beispiel #15
0
    def update_fernet_keys(self, new_keys):
        new_key_ids = []

        if not self._check_key_directory():
            raise exception.SysinvException(
                _("Error checking key repository."))

        try:
            for key in new_keys:
                self._create_key_file(key['id'], key['key'])
                new_key_ids.append(key['id'])

            # remove excess keys
            key_files = self._get_key_files()
            for key in key_files.keys():
                if key not in new_key_ids:
                    key_to_purge = key_files[key]
                    LOG.info('Purge excess key: %s', key_to_purge)
                    os.remove(key_to_purge)
        except Exception as e:
            msg = _("Failed to update fernet keys: %s") % six.text_type(e)
            LOG.exception(msg)
            raise exception.SysinvException(msg)
Beispiel #16
0
 def _patch_report_app_dependencies(self, name, patches=[]):
     try:
         system = self._dbapi.isystem_get_one()
         patch_api.patch_report_app_dependencies(
             token=None,
             timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
             region_name=system.region_name,
             patches=patches,
             app_name=name)
     except Exception as e:
         LOG.error(e)
         raise exception.SysinvException(
             "Error while reporting the patch dependencies "
             "to patch-controller.")
Beispiel #17
0
 def _check_patch_is_applied(self, patches):
     try:
         system = self._dbapi.isystem_get_one()
         response = patch_api.patch_is_applied(
             token=None,
             timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
             region_name=system.region_name,
             patches=patches)
     except Exception as e:
         LOG.error(e)
         raise exception.SysinvException(
             _("Error while querying patch-controller for the "
               "state of the patch(es)."))
     return response
Beispiel #18
0
 def _get_keyring_password(self, service, user, pw_format=None):
     password = keyring.get_password(service, user)
     if not password:
         if pw_format == common.PASSWORD_FORMAT_CEPH:
             try:
                 cmd = ['ceph-authtool', '--gen-print-key']
                 password = subprocess.check_output(cmd).strip()
             except subprocess.CalledProcessError:
                 raise exception.SysinvException(
                     'Failed to generate ceph key')
         else:
             password = self._generate_random_password()
         keyring.set_password(service, user, password)
     # get_password() returns in unicode format, which leads to YAML
     # that Armada doesn't like.  Converting to UTF-8 is safe because
     # we generated the password originally.
     return password.encode('utf8', 'strict')
Beispiel #19
0
def db_version():
    repository = _find_migrate_repo()
    try:
        return versioning_api.db_version(get_engine(), repository)
    except versioning_exceptions.DatabaseNotControlledError:
        meta = sqlalchemy.MetaData()
        engine = get_engine()
        meta.reflect(bind=engine)
        tables = meta.tables
        if len(tables) == 0:
            db_version_control(migration.INIT_VERSION)
            return versioning_api.db_version(get_engine(), repository)
        else:
            # Some pre-Essex DB's may not be version controlled.
            # Require them to upgrade using Essex first.
            raise exception.SysinvException(
                _("Upgrade DB using Essex release first."))
Beispiel #20
0
    def get_k8s_secret(secret_name, namespace=None):
        try:
            cmd = [
                'kubectl', '--kubeconfig=/etc/kubernetes/admin.conf', 'get',
                'secrets', secret_name
            ]
            if namespace:
                cmd.append('--namespace=%s' % namespace)
            stdout, _ = cutils.execute(*cmd, run_as_root=False)
        except exception.ProcessExecutionError as e:
            if "not found" in e.stderr.lower():
                return None
            raise exception.SysinvException(
                "Error getting secret: %s in namespace: %s, "
                "Details: %s" % (secret_name, namespace, str(e)))

        return stdout
Beispiel #21
0
    def get_host_config(self, host):
        config = {}

        # Retrieve labels for this host
        config.update(self._get_host_label_config(host))

        # Update cgroup resource controller parameters for this host
        config.update(self._get_host_k8s_cgroup_config(host))

        if host.personality != constants.WORKER:
            return config

        if self._kubernetes_enabled():
            create_node = False
            try:
                # Check if this host has already been configured as a
                # kubernetes node.
                cmd = [
                    'kubectl', '--kubeconfig=/etc/kubernetes/admin.conf',
                    'get', 'node', host.hostname
                ]
                subprocess.check_call(cmd)
            except subprocess.CalledProcessError:
                # The node does not exist
                create_node = True

            if create_node:
                try:
                    # Generate the token and join command for this host.
                    cmd = [
                        'kubeadm', 'token', 'create', '--print-join-command',
                        '--description',
                        'Bootstrap token for %s' % host.hostname
                    ]
                    join_cmd = subprocess.check_output(cmd)
                    config.update({
                        'platform::kubernetes::worker::params::join_cmd':
                        join_cmd,
                    })
                except subprocess.CalledProcessError:
                    raise exception.SysinvException(
                        'Failed to generate bootstrap token')

        return config
Beispiel #22
0
    def _check_patching_operation(self):
        try:
            system = self._dbapi.isystem_get_one()
            response = patch_api.patch_query(
                token=None,
                timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
                region_name=system.region_name)
            query_patches = response['pd']
        except Exception as e:
            # Assume that a patching operation is underway, raise an exception.
            LOG.error(_("No response from patch api: %s" % e))
            raise

        for patch in query_patches:
            patch_state = query_patches[patch].get('patchstate', None)
            if (patch_state == patch_constants.PARTIAL_APPLY
                    or patch_state == patch_constants.PARTIAL_REMOVE):
                raise exception.SysinvException(
                    _("Patching operation is in progress."))
Beispiel #23
0
def get_n3000_devices():
    # First get the PCI addresses of each supported FPGA device
    cmd = ["lspci", "-Dm", "-d " + constants.N3000_VENDOR + ":" +
           constants.N3000_DEVICE]

    try:
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as exc:
        msg = ("Failed to get pci devices with vendor %s and device %s, "
               "return code is %d, command output: %s." %
               (constants.N3000_VENDOR, constants.N3000_DEVICE, exc.returncode, exc.output))
        LOG.warn(msg)
        raise exception.SysinvException(msg)

    # Parse the output of the lspci command and grab the PCI address
    fpga_addrs = []
    for line in output.splitlines():
        line = shlex.split(line.strip())
        fpga_addrs.append(line[0])
    return fpga_addrs
Beispiel #24
0
 def _get_primary_cgtsclient(self):
     # import the module in the function that uses it
     # as the cgtsclient is only installed on the controllers
     from cgtsclient.v1 import client as cgts_client
     # get region one name from platform.conf
     region1_name = get_region_name('region_1_name')
     if region1_name is None:
         region1_name = 'RegionOne'
     auth_ref = self._get_keystoneclient(PLATFORM_CONFIG).auth_ref
     if auth_ref is None:
         raise exception.SysinvException(_("Unable to get auth ref "
                                         "from keystone client"))
     auth_token = auth_ref.service_catalog.get_token()
     endpoint = (auth_ref.service_catalog.
                 get_endpoints(service_type='platform',
                               endpoint_type='internal',
                               region_name=region1_name))
     endpoint = endpoint['platform'][0]
     version = 1
     return cgts_client.Client(version=version,
                               endpoint=endpoint['url'],
                               auth_url=self._get_auth_url(PLATFORM_CONFIG),
                               token=auth_token['id'])
Beispiel #25
0
def puppet_apply_manifest(ip_address,
                          personality,
                          manifest=None,
                          runtime=None,
                          do_reboot=False,
                          hieradata_path=PUPPET_HIERADATA_PATH):
    """ Apply configuration for the specified manifest."""
    if not manifest:
        manifest = personality

    cmd = [
        "/usr/local/bin/puppet-manifest-apply.sh", hieradata_path,
        str(ip_address), personality, manifest
    ]

    if runtime:
        cmd.append(runtime)

    try:
        if do_reboot:
            LOG.warn("Sysinv will be rebooting the node post "
                     "manifest application")

            with open("/dev/console", "w") as fconsole:
                cmdstr = " ".join(cmd) + ' && reboot'
                subprocess.Popen(cmdstr,
                                 stdout=fconsole,
                                 stderr=fconsole,
                                 shell=True)
        else:
            with open(os.devnull, "w") as fnull:
                subprocess.check_call(cmd, stdout=fnull, stderr=fnull)
    except subprocess.CalledProcessError:
        msg = "Failed to execute %s manifest for host %s" % \
              (manifest, ip_address)
        LOG.exception(msg)
        raise exception.SysinvException(_(msg))
    def _get_host_join_command(self, host):
        config = {}

        if host.personality != constants.WORKER:
            return config

        # The token expires after 24 hours and is needed for a reinstall.
        # The puppet manifest handles the case where the node already exists.
        try:
            cmd = [
                'kubeadm', 'token', 'create', '--print-join-command',
                '--description',
                'Bootstrap token for %s' % host.hostname
            ]
            join_cmd = subprocess.check_output(cmd)
            config.update({
                'platform::kubernetes::worker::params::join_cmd':
                join_cmd,
            })
        except subprocess.CalledProcessError:
            raise exception.SysinvException(
                'Failed to generate bootstrap token')

        return config
Beispiel #27
0
    def _get_host_join_command(self, host):
        config = {}
        if not utils.is_initial_config_complete():
            return config

        # The token expires after 24 hours and is needed for a reinstall.
        # The puppet manifest handles the case where the node already exists.
        try:
            join_cmd_additions = ''
            if host.personality == constants.CONTROLLER:
                # Upload the certificates used during kubeadm join
                # The cert key will be printed in the last line of the output
                cmd = [
                    'kubeadm', 'init', 'phase', 'upload-certs',
                    '--upload-certs', '--config',
                    '/etc/kubernetes/kubeadm.yaml'
                ]
                cmd_output = subprocess.check_output(cmd)
                cert_key = cmd_output.strip().split('\n')[-1]
                join_cmd_additions = " --control-plane --certificate-key %s" % cert_key

            cmd = [
                'kubeadm', 'token', 'create', '--print-join-command',
                '--description',
                'Bootstrap token for %s' % host.hostname
            ]
            join_cmd = subprocess.check_output(cmd)
            join_cmd_additions += " --cri-socket /var/run/containerd/containerd.sock"
            join_cmd = join_cmd.strip() + join_cmd_additions
        except subprocess.CalledProcessError:
            raise exception.SysinvException(
                'Failed to generate bootstrap token')

        config.update({'platform::kubernetes::params::join_cmd': join_cmd})

        return config
Beispiel #28
0
    def get_system_config(self):
        ceph_backend = StorageBackendConfig.get_backend_conf(
            self.dbapi, constants.CINDER_BACKEND_CEPH)
        if not ceph_backend:
            return {}  # ceph is not configured

        ceph_mon_ips = StorageBackendConfig.get_ceph_mon_ip_addresses(
            self.dbapi)

        if not ceph_mon_ips:
            return {}  # system configuration is not yet ready

        controller_hosts = [
            constants.CONTROLLER_0_HOSTNAME, constants.CONTROLLER_1_HOSTNAME
        ]
        mon_2_host = [
            mon['hostname'] for mon in self.dbapi.ceph_mon_get_list()
            if mon['hostname'] not in controller_hosts
        ]
        if len(mon_2_host) > 1:
            raise exception.SysinvException(
                'Too many ceph monitor hosts, expected 1, got: %s.' %
                mon_2_host)
        if mon_2_host:
            mon_2_host = mon_2_host[0]
        else:
            mon_2_host = None

        mon_0_ip = ceph_mon_ips[constants.CEPH_MON_0]
        mon_1_ip = ceph_mon_ips[constants.CEPH_MON_1]
        mon_2_ip = ceph_mon_ips.get(constants.CEPH_MON_2, None)
        floating_mon_ip = ceph_mon_ips[constants.CEPH_FLOATING_MON]

        mon_0_addr = self._format_ceph_mon_address(mon_0_ip)
        mon_1_addr = self._format_ceph_mon_address(mon_1_ip)
        if mon_2_ip:
            mon_2_addr = self._format_ceph_mon_address(mon_2_ip)
        else:
            mon_2_addr = None
        floating_mon_addr = self._format_ceph_mon_address(floating_mon_ip)

        # ceph can not bind to multiple address families, so only enable IPv6
        # if the monitors are IPv6 addresses
        ms_bind_ipv6 = (
            netaddr.IPAddress(mon_0_ip).version == constants.IPV6_FAMILY)

        skip_osds_during_restore = \
            (utils.is_std_system(self.dbapi) and
            ceph_backend.task == constants.SB_TASK_RESTORE)

        is_sx_to_dx_migration = self._get_system_capability(
            'simplex_to_duplex_migration')

        config = {
            'ceph::ms_bind_ipv6':
            ms_bind_ipv6,
            'platform::ceph::params::service_enabled':
            True,
            'platform::ceph::params::floating_mon_host':
            constants.CONTROLLER_HOSTNAME,
            'platform::ceph::params::mon_0_host':
            constants.CONTROLLER_0_HOSTNAME,
            'platform::ceph::params::mon_1_host':
            constants.CONTROLLER_1_HOSTNAME,
            'platform::ceph::params::mon_2_host':
            mon_2_host,
            'platform::ceph::params::floating_mon_ip':
            floating_mon_ip,
            'platform::ceph::params::mon_0_ip':
            mon_0_ip,
            'platform::ceph::params::mon_1_ip':
            mon_1_ip,
            'platform::ceph::params::mon_2_ip':
            mon_2_ip,
            'platform::ceph::params::floating_mon_addr':
            floating_mon_addr,
            'platform::ceph::params::mon_0_addr':
            mon_0_addr,
            'platform::ceph::params::mon_1_addr':
            mon_1_addr,
            'platform::ceph::params::mon_2_addr':
            mon_2_addr,
            'platform::ceph::params::rgw_enabled':
            self._is_radosgw_enabled(),
            'platform::ceph::rgw::keystone::swift_endpts_enabled':
            False,
            'platform::ceph::rgw::keystone::rgw_admin_user':
            self._get_service_user_name(self.SERVICE_NAME_RGW),
            'platform::ceph::rgw::keystone::rgw_admin_password':
            self._get_service_password(self.SERVICE_NAME_RGW),
            'platform::ceph::rgw::keystone::rgw_admin_domain':
            self._get_service_user_domain_name(),
            'platform::ceph::rgw::keystone::rgw_admin_project':
            self._get_service_tenant_name(),
            'platform::ceph::params::skip_osds_during_restore':
            skip_osds_during_restore,
            'platform::ceph::params::simplex_to_duplex_migration':
            bool(is_sx_to_dx_migration),
        }

        if is_sx_to_dx_migration:
            cephfs_filesystems = self._get_cephfs_filesystems()
            if cephfs_filesystems:
                config[
                    'platform::ceph::params::cephfs_filesystems'] = cephfs_filesystems

        if (utils.is_openstack_applied(self.dbapi) and utils.is_chart_enabled(
                self.dbapi, constants.HELM_APP_OPENSTACK,
                self.HELM_CHART_SWIFT, common.HELM_NS_OPENSTACK)):
            app = self.dbapi.kube_app_get(constants.HELM_APP_OPENSTACK)
            override = self.dbapi.helm_override_get(app.id,
                                                    self.SERVICE_NAME_RGW,
                                                    common.HELM_NS_OPENSTACK)
            password = override.system_overrides.get(self.SERVICE_NAME_RGW,
                                                     None)
            if password:
                swift_auth_password = password.encode('utf8', 'strict')
                config.update({
                    'platform::ceph::rgw::keystone::swift_endpts_enabled':
                    True
                })
                config.pop('platform::ceph::rgw::keystone::rgw_admin_user')
                config.update({
                    'platform::ceph::rgw::keystone::rgw_admin_password':
                    swift_auth_password
                })
                config.update({
                    'platform::ceph::rgw::keystone::rgw_admin_domain':
                    self.RADOSGW_SERVICE_DOMAIN_NAME
                })
                config.update({
                    'platform::ceph::rgw::keystone::rgw_admin_project':
                    self.RADOSGW_SERVICE_PROJECT_NAME
                })
            else:
                raise exception.SysinvException(
                    "Unable to retreive containerized swift auth password")

        return config
Beispiel #29
0
    def post(self, host_uuid, body):
        """Update the kubernetes root CA certificate on this host"""

        # Check cluster update status
        try:
            update = pecan.request.dbapi.kube_rootca_update_get_one()
        except exception.NotFound:
            raise wsme.exc.ClientSideError(
                _("kube-rootca-host-update rejected: No update in progress."))

        # Check if the new root CA cert secret exists, in case the secret
        # is deleted unexpectly.
        kube_operator = kubernetes.KubeOperator()
        try:
            cert_secret = kube_operator.kube_get_secret(
                constants.KUBE_ROOTCA_SECRET,
                kubernetes.NAMESPACE_DEPLOYMENT,
            )
        except Exception:
            raise wsme.exc.ClientSideError(
                _("kube-rootca-host-update rejected: failed to get new root CA "
                  "cert secret from kubernetes."))

        if cert_secret is None:
            raise wsme.exc.ClientSideError(
                _("kube-rootca-host-update rejected: no new root CA cert found."
                  ))

        ihost = pecan.request.dbapi.ihost_get(host_uuid)

        if body['phase'].lower() == constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS:
            # kube root CA update on host phase trust-both-cas
            self._precheck_trustbothcas(update, ihost)
            update_state = kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS
        else:
            raise wsme.exc.ClientSideError(
                _("kube-rootca-host-update rejected: not supported phase."))

        # Update the cluster update state
        c_values = dict()
        c_values['state'] = update_state
        c_update = pecan.request.dbapi.kube_rootca_update_get_one()
        pecan.request.dbapi.kube_rootca_update_update(c_update.id, c_values)

        # Create or update "update state" on this host
        h_values = dict()
        h_values['state'] = update_state
        h_values['effective_rootca_cert'] = c_update.from_rootca_cert
        h_values['target_rootca_cert'] = c_update.to_rootca_cert
        try:
            h_update = pecan.request.dbapi.kube_rootca_host_update_get_by_host(
                ihost.id)
            h_update = pecan.request.dbapi.kube_rootca_host_update_update(
                h_update.id, h_values)
        except exception.NotFound:
            h_update = pecan.request.dbapi.kube_rootca_host_update_create(
                ihost.id, h_values)

        phase = body['phase'].lower()
        if phase not in [
                constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS,
                constants.KUBE_CERT_UPDATE_UPDATECERTS,
                constants.KUBE_CERT_UPDATE_TRUSTNEWCA
        ]:
            raise exception.SysinvException(
                _("Invalid phase %s to update kube certificate." % phase))

        # perform rpc to conductor to perform config apply
        pecan.request.rpcapi.kube_certificate_update_by_host(
            pecan.request.context, host_uuid, body['phase'])

        LOG.info("Kubernetes rootca update started on host: %s" %
                 ihost.hostname)

        return KubeRootCAHostUpdate.convert_with_links(h_update)
Beispiel #30
0
    def get_secure_static_config(self):
        ssh_config_dir = os.path.join(self.CONFIG_WORKDIR, 'ssh_config')
        migration_key = os.path.join(ssh_config_dir, 'nova_migration_key')
        system_host_key = os.path.join(ssh_config_dir, 'system_host_key')

        # Generate the keys.
        if os.path.exists(ssh_config_dir):
            shutil.rmtree(ssh_config_dir)

        os.makedirs(ssh_config_dir)

        try:
            cmd = [
                'ssh-keygen', '-t', 'rsa', '-b'
                '2048', '-N', '', '-f', migration_key
            ]
            with open(os.devnull, "w") as fnull:
                subprocess.check_call(cmd, stdout=fnull, stderr=fnull)
        except subprocess.CalledProcessError:
            raise exception.SysinvException('Failed to generate nova rsa key')

        # Generate an ecdsa key for the system, which will be used on all
        # controller/worker nodes. When external ssh connections to the
        # controllers are made, this key will be stored in the known_hosts file
        # and allow connections after the controller swacts. The ecdsa key
        # has precedence over the rsa key, which is why we use ecdsa.
        try:
            cmd = [
                'ssh-keygen', '-t', 'ecdsa', '-b', '256', '-N', '', '-f',
                system_host_key
            ]
            with open(os.devnull, "w") as fnull:
                subprocess.check_call(cmd, stdout=fnull, stderr=fnull)
        except subprocess.CalledProcessError:
            raise exception.SysinvException(
                'Failed to generate nova ecdsa key')

        # Read the public/private migration keys
        with open(migration_key) as fp:
            migration_private = fp.read().strip()
        with open('%s.pub' % migration_key) as fp:
            migration_header, migration_public, _ = fp.read().strip().split()

        # Read the public/private host keys
        with open(system_host_key) as fp:
            host_private = fp.read().strip()
        with open('%s.pub' % system_host_key) as fp:
            host_header, host_public, _ = fp.read().strip().split()

        # Add our pre-generated system host key to /etc/ssh/ssh_known_hosts
        ssh_keys = {
            'system_host_key': {
                'ensure': 'present',
                'name': '*',
                'host_aliases': [],
                'type': host_header,
                'key': host_public
            }
        }

        dbpass = self._get_database_password(self.SERVICE_NAME)
        kspass = self._get_service_password(self.SERVICE_NAME)
        kspass_placement = self._get_service_password(self.PLACEMENT_NAME)

        api_dbpass = self._get_database_password(self.SERVICE_API_NAME)

        return {
            'nova::db::postgresql::password':
            dbpass,
            'nova::db::postgresql_api::password':
            api_dbpass,
            'nova::keystone::auth::password':
            kspass,
            'nova::keystone::auth_placement::password':
            kspass_placement,
            'nova::keystone::authtoken::password':
            kspass,
            'nova::api::neutron_metadata_proxy_shared_secret':
            self._get_service_password(self.SERVICE_METADATA),
            'nova_api_proxy::config::admin_password':
            kspass,
            'nova::network::neutron::neutron_password':
            self._get_neutron_password(),
            'nova::placement::password':
            self._get_placement_password(),
            'openstack::nova::compute::ssh_keys':
            ssh_keys,
            'openstack::nova::compute::host_key_type':
            'ssh-ecdsa',
            'openstack::nova::compute::host_private_key':
            host_private,
            'openstack::nova::compute::host_public_key':
            host_public,
            'openstack::nova::compute::host_public_header':
            host_header,
            'openstack::nova::compute::migration_key_type':
            'ssh-rsa',
            'openstack::nova::compute::migration_private_key':
            migration_private,
            'openstack::nova::compute::migration_public_key':
            migration_public,
        }