def _update(self, cache_only): """ update entitlement certificates """ logger.info(_('Updating Subscription Management repositories.')) # XXX: Importing inline as you must be root to read the config file from subscription_manager.identity import ConsumerIdentity cert_file = str(ConsumerIdentity.certpath()) key_file = str(ConsumerIdentity.keypath()) identity = inj.require(inj.IDENTITY) # In containers we have no identity, but we may have entitlements inherited # from the host, which need to generate a redhat.repo. if identity.is_valid(): try: connection.UEPConnection(cert_file=cert_file, key_file=key_file) # FIXME: catchall exception except Exception: # log logger.info(_("Unable to connect to Subscription Management Service")) return else: logger.info(_("Unable to read consumer identity")) if config.in_container(): logger.info(_("Subscription Manager is operating in container mode.")) if not cache_only: cert_action_invoker = EntCertActionInvoker() cert_action_invoker.update() repo_action_invoker = RepoActionInvoker(cache_only=cache_only) repo_action_invoker.update()
def RegisterWithActivationKeys(self, org, activation_keys, options, connection_options, locale): """ Note this method is registration ONLY. Auto-attach is a separate process. """ connection_options = dbus_utils.dbus_to_python(connection_options, expected_type=dict) options = dbus_utils.dbus_to_python(options, expected_type=dict) options["activation_keys"] = dbus_utils.dbus_to_python( activation_keys, expected_type=list) org = dbus_utils.dbus_to_python(org, expected_type=str) locale = dbus_utils.dbus_to_python(locale, expected_type=str) with DBusSender() as dbus_sender: dbus_sender.set_cmd_line(sender=self.sender, cmd_line=self.cmd_line) Locale.set(locale) cp = self.build_uep(connection_options) register_service = RegisterService(cp) consumer = register_service.register(org, **options) log.debug("System registered, updating entitlements if needed") ent_cert_lib = EntCertActionInvoker() ent_cert_lib.update() dbus_sender.reset_cmd_line() return json.dumps(consumer)
def _update(cache_only): """ Update entitlement certificates and redhat.repo :param cache_only: is True, when rhsm.full_refresh_on_yum is set to 0 in rhsm.conf """ logger.info(_('Updating Subscription Management repositories.')) identity = inj.require(inj.IDENTITY) if not identity.is_valid(): logger.info(_("Unable to read consumer identity")) if config.in_container(): logger.info( _("Subscription Manager is operating in container mode.")) if cache_only is True: log.debug('DNF subscription-manager operates in cache-only mode') if not cache_only and not config.in_container(): log.debug( 'Trying to update entitlement certificates and redhat.repo') cert_action_invoker = EntCertActionInvoker() cert_action_invoker.update() else: log.debug('Skipping updating of entitlement certificates') log.debug('Generating redhat.repo') repo_action_invoker = RepoActionInvoker(cache_only=cache_only) repo_action_invoker.update()
def update(conduit, cache_only): """ Update entitlement certificates """ if os.getuid() != 0: conduit.info( 3, 'Not root, Subscription Management repositories not updated') return conduit.info(3, 'Updating Subscription Management repositories.') identity = inj.require(inj.IDENTITY) if not identity.is_valid(): conduit.info(3, "Unable to read consumer identity") # In containers we have no identity, but we may have entitlements inherited # from the host, which need to generate a redhat.repo. if config.in_container(): conduit.info(3, "Subscription Manager is operating in container mode.") if not cache_only and not config.in_container(): cert_action_invoker = EntCertActionInvoker(locker=YumRepoLocker( conduit=conduit)) cert_action_invoker.update() if cache_only or config.in_container(): repo_action_invoker = RepoActionInvoker( cache_only=cache_only, locker=YumRepoLocker(conduit=conduit)) repo_action_invoker.update()
def _get_libset(self): self.entcertlib = EntCertActionInvoker() self.repolib = RepoActionInvoker() lib_set = [self.entcertlib, self.repolib] return lib_set
def _get_libset(self): self.entcertlib = EntCertActionInvoker() self.content_action_client = ContentActionClient() lib_set = [self.entcertlib, self.content_action_client] return lib_set
def _get_libset(self): # TODO: replace with FSM thats progress through this async and wait/joins if needed self.entcertlib = EntCertActionInvoker() self.content_client = ContentActionClient() self.factlib = FactsActionInvoker() self.profilelib = PackageProfileActionInvoker() self.installedprodlib = InstalledProductsActionInvoker() self.idcertlib = IdentityCertActionInvoker() self.syspurposelib = SyspurposeSyncActionInvoker() # WARNING: order is important here, we need to update a number # of things before attempting to autoheal, and we need to autoheal # before attempting to fetch our certificates: lib_set = [ self.entcertlib, self.idcertlib, self.content_client, self.factlib, self.profilelib, self.installedprodlib, self.syspurposelib, ] return lib_set
def _get_libset(self): self.entcertlib = EntCertActionInvoker() self.installedprodlib = InstalledProductsActionInvoker() self.healinglib = HealingActionInvoker() lib_set = [self.installedprodlib, self.healinglib, self.entcertlib] return lib_set
def update(conduit, cache_only): """ Update entitlement certificates """ if os.getuid() != 0: conduit.info( 3, 'Not root, Subscription Management repositories not updated') return conduit.info(3, 'Updating Subscription Management repositories.') # XXX: Importing inline as you must be root to read the config file from subscription_manager.identity import ConsumerIdentity cert_file = ConsumerIdentity.certpath() key_file = ConsumerIdentity.keypath() identity = inj.require(inj.IDENTITY) # In containers we have no identity, but we may have entitlements inherited # from the host, which need to generate a redhat.repo. if identity.is_valid(): if not cache_only: try: connection.UEPConnection(cert_file=cert_file, key_file=key_file) except Exception: # log conduit.info( 2, "Unable to connect to Subscription Management Service") return else: conduit.info(3, "Unable to read consumer identity") if config.in_container(): conduit.info(3, "Subscription Manager is operating in container mode.") if not cache_only and not config.in_container(): cert_action_invoker = EntCertActionInvoker(locker=YumRepoLocker( conduit=conduit)) cert_action_invoker.update() repo_action_invoker = RepoActionInvoker( cache_only=cache_only, locker=YumRepoLocker(conduit=conduit)) repo_action_invoker.update()
def create_uep(self): # Re-initialize our connection: self.cp_provider.set_connection_info() # These objects hold a reference to the old uep and must be updated: # FIXME: We should find a way to update the connection so that the # conncection objects are refreshed rather than recreated. self.certlib = EntCertActionInvoker() self.overrides = Overrides()
def __init__(self): self.identity = require(IDENTITY) self.cp_provider = require(CP_PROVIDER) self.update() self.product_dir = inj.require(inj.PROD_DIR) self.entitlement_dir = inj.require(inj.ENT_DIR) self.certlib = EntCertActionInvoker() self.overrides = Overrides() self.cs = require(CERT_SORTER)
def update(conduit, cache_only): """ update entitlement certificates """ if os.getuid() != 0: conduit.info(3, 'Not root, Subscription Management repositories not updated') return conduit.info(3, 'Updating Subscription Management repositories.') # XXX: Importing inline as you must be root to read the config file from subscription_manager.identity import ConsumerIdentity cert_file = ConsumerIdentity.certpath() key_file = ConsumerIdentity.keypath() identity = inj.require(inj.IDENTITY) # In containers we have no identity, but we may have entitlements inherited # from the host, which need to generate a redhat.repo. if identity.is_valid(): try: connection.UEPConnection(cert_file=cert_file, key_file=key_file) # FIXME: catchall exception except Exception: # log conduit.info(2, "Unable to connect to Subscription Management Service") return else: conduit.info(3, "Unable to read consumer identity") if config.in_container(): conduit.info(3, "Subscription Manager is operating in container mode.") if not cache_only: cert_action_invoker = EntCertActionInvoker(locker=YumRepoLocker(conduit=conduit)) cert_action_invoker.update() repo_action_invoker = RepoActionInvoker(cache_only=cache_only, locker=YumRepoLocker(conduit=conduit)) repo_action_invoker.update()
def _update(self, cache_only): """ update entitlement certificates """ logger.info(_('Updating Subscription Management repositories.')) # XXX: Importing inline as you must be root to read the config file from subscription_manager.identity import ConsumerIdentity cert_file = str(ConsumerIdentity.certpath()) key_file = str(ConsumerIdentity.keypath()) identity = inj.require(inj.IDENTITY) # In containers we have no identity, but we may have entitlements inherited # from the host, which need to generate a redhat.repo. if identity.is_valid(): try: connection.UEPConnection(cert_file=cert_file, key_file=key_file) # FIXME: catchall exception except Exception: # log logger.info( _("Unable to connect to Subscription Management Service")) return else: logger.info(_("Unable to read consumer identity")) if config.in_container(): logger.info( _("Subscription Manager is operating in container mode.")) if not cache_only and not config.in_container(): cert_action_invoker = EntCertActionInvoker() cert_action_invoker.update() repo_action_invoker = RepoActionInvoker(cache_only=cache_only) repo_action_invoker.update()
def _get_libset(self): self.entcertlib = EntCertActionInvoker() self.repolib = RepoActionInvoker() self.factlib = FactsActionInvoker() self.profilelib = PackageProfileActionInvoker() self.installedprodlib = InstalledProductsActionInvoker() self.idcertlib = IdentityCertActionInvoker() # WARNING: order is important here, we need to update a number # of things before attempting to autoheal, and we need to autoheal # before attempting to fetch our certificates: lib_set = [ self.entcertlib, self.idcertlib, self.repolib, self.factlib, self.profilelib, self.installedprodlib ] return lib_set
def __init__(self, cp=None): self.cp = cp self.identity = inj.require(inj.IDENTITY) self.product_dir = inj.require(inj.PROD_DIR) self.entitlement_dir = inj.require(inj.ENT_DIR) self.entcertlib = EntCertActionInvoker()
class EntitlementService(object): def __init__(self, cp=None): self.cp = cp self.identity = inj.require(inj.IDENTITY) self.product_dir = inj.require(inj.PROD_DIR) self.entitlement_dir = inj.require(inj.ENT_DIR) self.entcertlib = EntCertActionInvoker() @classmethod def parse_date(cls, on_date): """ Return new datetime parsed from date :param on_date: String representing date :return It returns datetime.datime structure representing date """ try: on_date = datetime.datetime.strptime(on_date, "%Y-%m-%d") except ValueError: raise ValueError( _("Date entered is invalid. Date should be in YYYY-MM-DD format (example: " ) + time.strftime("%Y-%m-%d", time.localtime()) + " )") if on_date.date() < datetime.datetime.now().date(): raise ValueError(_("Past dates are not allowed")) return on_date def get_status(self, on_date=None, force=False): sorter = inj.require(inj.CERT_SORTER, on_date) # When singleton CertSorter was created with different argument on_date, then # it is necessary to update corresponding attribute in object (dependency # injection doesn't do it automatically). if sorter.on_date != on_date: sorter.on_date = on_date # Force reload status from the server to be sure that we get valid status for new date. # It is necessary to do it for rhsm.service, because it can run for very long time without # restart. if force is True: log.debug("Deleting cache entitlement status cache") status_cache = inj.require(inj.ENTITLEMENT_STATUS_CACHE) status_cache.server_status = None status_cache.delete_cache() sorter.load() if self.identity.is_valid(): overall_status = sorter.get_system_status() overall_status_id = sorter.get_system_status_id() reasons = sorter.reasons.get_name_message_map() reason_ids = sorter.reasons.get_reason_ids_map() valid = sorter.is_valid() status = { "status": overall_status, "status_id": overall_status_id, "reasons": reasons, "reason_ids": reason_ids, "valid": valid, } else: status = { "status": _("Unknown"), "status_id": "unknown", "reasons": {}, "reason_ids": {}, "valid": False, } log.debug("entitlement status: %s" % str(status)) return status def get_pools(self, pool_subsets=None, matches=None, pool_only=None, match_installed=None, no_overlap=None, service_level=None, show_all=None, on_date=None, future=None, after_date=None, page=0, items_per_page=0, **kwargs): # We accept a **kwargs argument so that the DBus object can pass whatever dictionary it receives # via keyword expansion. if kwargs: raise exceptions.ValidationError( _("Unknown arguments: %s") % kwargs.keys()) if isinstance(pool_subsets, str): pool_subsets = [pool_subsets] # [] or None means look at all pools if not pool_subsets: pool_subsets = ["installed", "consumed", "available"] options = { "pool_subsets": pool_subsets, "matches": matches, "pool_only": pool_only, "match_installed": match_installed, "no_overlap": no_overlap, "service_level": service_level, "show_all": show_all, "on_date": on_date, "future": future, "after_date": after_date, } self.validate_options(options) results = {} if "installed" in pool_subsets: installed = products.InstalledProducts(self.cp).list( matches, iso_dates=True) results["installed"] = [x._asdict() for x in installed] if "consumed" in pool_subsets: consumed = self.get_consumed_product_pools( service_level=service_level, matches=matches, iso_dates=True) if pool_only: results["consumed"] = [ x._asdict()["pool_id"] for x in consumed ] else: results["consumed"] = [x._asdict() for x in consumed] if "available" in pool_subsets: available = self.get_available_pools( show_all=show_all, on_date=on_date, no_overlap=no_overlap, match_installed=match_installed, matches=matches, service_level=service_level, future=future, after_date=after_date, page=int(page), items_per_page=int(items_per_page), iso_dates=True, ) if pool_only: results["available"] = [x["id"] for x in available] else: results["available"] = available return results def get_consumed_product_pools(self, service_level=None, matches=None, iso_dates=False): # Use a named tuple so that the result can be unpacked into other functions OldConsumedStatus = collections.namedtuple( "OldConsumedStatus", [ "subscription_name", "provides", "sku", "contract", "account", "serial", "pool_id", "provides_management", "active", "quantity_used", "service_type", "service_level", "status_details", "subscription_type", "starts", "ends", "system_type", ], ) # Use a named tuple so that the result can be unpacked into other functions ConsumedStatus = collections.namedtuple( "ConsumedStatus", [ "subscription_name", "provides", "sku", "contract", "account", "serial", "pool_id", "provides_management", "active", "quantity_used", "service_type", "roles", "service_level", "usage", "addons", "status_details", "subscription_type", "starts", "ends", "system_type", ], ) sorter = inj.require(inj.CERT_SORTER) cert_reasons_map = sorter.reasons.get_subscription_reasons_map() pooltype_cache = inj.require(inj.POOLTYPE_CACHE) consumed_statuses = [] # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. When new certificate is installed/deleted and rhsm-service # is running, then list of certificate is not automatically refreshed ATM. self.entitlement_dir.refresh() certs = self.entitlement_dir.list() cert_filter = utils.EntitlementCertificateFilter( filter_string=matches, service_level=service_level) if service_level is not None or matches is not None: certs = list(filter(cert_filter.match, certs)) if iso_dates: date_formatter = managerlib.format_iso8601_date else: date_formatter = managerlib.format_date # Now we need to transform the EntitlementCertificate object into # something JSON-like for consumption for cert in certs: # for some certs, order can be empty # so we default the values and populate them if # they exist. BZ974587 name = "" sku = "" contract = "" account = "" quantity_used = "" service_type = "" roles = "" service_level = "" usage = "" addons = "" system_type = "" provides_management = "No" order = cert.order if order: service_type = order.service_type or "" service_level = order.service_level or "" if cert.version.major >= 3 and cert.version.minor >= 4: roles = order.roles or "" usage = order.usage or "" addons = order.addons or "" else: roles = None usage = None addons = None name = order.name sku = order.sku contract = order.contract or "" account = order.account or "" quantity_used = order.quantity_used if order.virt_only: system_type = _("Virtual") else: system_type = _("Physical") if order.provides_management: provides_management = _("Yes") else: provides_management = _("No") pool_id = _("Not Available") if hasattr(cert.pool, "id"): pool_id = cert.pool.id provided_products = {p.id: p.name for p in cert.products} reasons = [] pool_type = "" if inj.require(inj.CERT_SORTER).are_reasons_supported(): if cert.subject and "CN" in cert.subject: if cert.subject["CN"] in cert_reasons_map: reasons = cert_reasons_map[cert.subject["CN"]] pool_type = pooltype_cache.get(pool_id) # 1180400: Status details is empty when GUI is not if not reasons: if cert in sorter.valid_entitlement_certs: reasons.append(_("Subscription is current")) else: if cert.valid_range.end() < datetime.datetime.now( certificate.GMT()): reasons.append(_("Subscription is expired")) else: reasons.append(_("Subscription has not begun")) else: reasons.append( _("Subscription management service doesn't support Status Details." )) if roles is None and usage is None and addons is None: consumed_statuses.append( OldConsumedStatus( name, provided_products, sku, contract, account, cert.serial, pool_id, provides_management, cert.is_valid(), quantity_used, service_type, service_level, reasons, pool_type, date_formatter(cert.valid_range.begin()), date_formatter(cert.valid_range.end()), system_type, )) else: consumed_statuses.append( ConsumedStatus( name, provided_products, sku, contract, account, cert.serial, pool_id, provides_management, cert.is_valid(), quantity_used, service_type, roles, service_level, usage, addons, reasons, pool_type, date_formatter(cert.valid_range.begin()), date_formatter(cert.valid_range.end()), system_type, )) return consumed_statuses def get_available_pools( self, show_all=None, on_date=None, no_overlap=None, match_installed=None, matches=None, service_level=None, future=None, after_date=None, page=0, items_per_page=0, iso_dates=False, ): """ Get list of available pools :param show_all: :param on_date: :param no_overlap: :param match_installed: :param matches: :param service_level: :param future: :param after_date: :param page: :param items_per_page: :param iso_dates: :return: """ # Values used for REST API calls and caching are bigger, because it makes using of cache and # API more efficient if show_all is not True: _page = int(page / 4) _items_per_page = 4 * items_per_page else: page = items_per_page = 0 _page = _items_per_page = 0 filter_options = { "show_all": show_all, "on_date": on_date, "no_overlap": no_overlap, "match_installed": match_installed, "matches": matches, "service_level": service_level, "future": future, "after_date": after_date, "page": _page, "items_per_page": _items_per_page, } # Try to get identity identity = inj.require(inj.IDENTITY) # Try to get available pools from cache cache = inj.require(inj.AVAILABLE_ENTITLEMENT_CACHE) available_pools = cache.get_not_obsolete_data(identity, filter_options) if len(available_pools) == 0: available_pools = managerlib.get_available_entitlements( get_all=show_all, active_on=on_date, overlapping=no_overlap, uninstalled=match_installed, filter_string=matches, future=future, after_date=after_date, page=_page, items_per_page=_items_per_page, iso_dates=iso_dates, ) timeout = cache.timeout() data = { identity.uuid: { "filter_options": filter_options, "pools": available_pools, "timeout": time.time() + timeout, } } cache.available_entitlements = data cache.write_cache() def filter_pool_by_service_level(pool_data): pool_level = "" if pool_data["service_level"]: pool_level = pool_data["service_level"] return service_level.lower() == pool_level.lower() if service_level is not None: available_pools = list( filter(filter_pool_by_service_level, available_pools)) # When pagination result of available pools is requested, then reduce too long list if items_per_page > 0: # Reduce too long list to requested "page" lo_idx = (page * items_per_page) % _items_per_page hi_idx = ((page + 1) * items_per_page) % _items_per_page if hi_idx == 0: hi_idx = _items_per_page # Own filtering of the list available_pools = available_pools[lo_idx:hi_idx] # Add requested page and number of items per page to the result too for item in available_pools: item["page"] = page item["items_per_page"] = items_per_page return available_pools def validate_options(self, options): if not set(["installed", "consumed", "available"]).issuperset( options["pool_subsets"]): raise exceptions.ValidationError( _('Error: invalid listing type provided. Only "installed", ' '"consumed", or "available" are allowed')) if options["show_all"] and "available" not in options["pool_subsets"]: raise exceptions.ValidationError( _("Error: --all is only applicable with --available")) elif options["on_date"] and "available" not in options["pool_subsets"]: raise exceptions.ValidationError( _("Error: --ondate is only applicable with --available")) elif options["service_level"] is not None and not set( ["consumed", "available"]).intersection(options["pool_subsets"]): raise exceptions.ValidationError( _("Error: --servicelevel is only applicable with --available or --consumed" )) elif options["match_installed"] and "available" not in options[ "pool_subsets"]: raise exceptions.ValidationError( _("Error: --match-installed is only applicable with --available" )) elif options["no_overlap"] and "available" not in options[ "pool_subsets"]: raise exceptions.ValidationError( _("Error: --no-overlap is only applicable with --available")) elif options["pool_only"] and not set( ["consumed", "available"]).intersection(options["pool_subsets"]): raise exceptions.ValidationError( _("Error: --pool-only is only applicable with --available and/or --consumed" )) elif not self.identity.is_valid( ) and "available" in options["pool_subsets"]: raise exceptions.ValidationError( _("Error: this system is not registered")) def _unbind_ids(self, unbind_method, consumer_uuid, ids): """ Method for unbinding entitlements :param unbind_method: unbindByPoolId or unbindBySerial :param consumer_uuid: UUID of consumer :param ids: List of serials or pool_ids :return: Tuple of two lists containing unbinded and not-unbinded subscriptions """ success = [] failure = [] for id_ in ids: try: unbind_method(consumer_uuid, id_) success.append(id_) except connection.RestlibException as re: if re.code == 410: raise failure.append(id_) log.error(re) return success, failure def remove_all_entitlements(self): """ Try to remove all entilements :return: Result of REST API call """ response = self.cp.unbindAll(self.identity.uuid) self.entcertlib.update() return response def remove_entilements_by_pool_ids(self, pool_ids): """ Try to remove entitlements by pool IDs :param pool_ids: List of pool IDs :return: List of serial numbers of removed subscriptions """ removed_serials = [] _pool_ids = utils.unique_list_items(pool_ids) # Don't allow duplicates # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. I vote for i-notify to be used there somehow. self.entitlement_dir.refresh() pool_id_to_serials = self.entitlement_dir.list_serials_for_pool_ids( _pool_ids) removed_pools, unremoved_pools = self._unbind_ids( self.cp.unbindByPoolId, self.identity.uuid, _pool_ids) if removed_pools: for pool_id in removed_pools: removed_serials.extend(pool_id_to_serials[pool_id]) self.entcertlib.update() return removed_pools, unremoved_pools, removed_serials def remove_entitlements_by_serials(self, serials): """ Try to remove pools by Serial numbers :param serials: List of serial numbers :return: List of serial numbers of already removed subscriptions """ _serials = utils.unique_list_items(serials) # Don't allow duplicates removed_serials, unremoved_serials = self._unbind_ids( self.cp.unbindBySerial, self.identity.uuid, _serials) self.entcertlib.update() return removed_serials, unremoved_serials def reload(self): """ This callback function is called, when there is detected any change in directory with entitlement certificates (e.g. certificate is installed or removed) :return: """ sorter = inj.require(inj.CERT_SORTER, on_date=None) status_cache = inj.require(inj.ENTITLEMENT_STATUS_CACHE) log.debug("Clearing in-memory cache of file %s" % status_cache.CACHE_FILE) status_cache.server_status = None sorter.load() def refresh(self, remove_cache=False, force=False): """ Try to refresh entitlement certificate(s) from candlepin server :return: Report of EntCertActionInvoker """ if remove_cache is True: # remove content_access cache, ensuring we get it fresh content_access = inj.require(inj.CONTENT_ACCESS_CACHE) if content_access.exists(): content_access.remove() # Also remove the content access mode cache to be sure we display # SCA or regular mode correctly content_access_mode = inj.require(inj.CONTENT_ACCESS_MODE_CACHE) if content_access_mode.exists(): content_access_mode.delete_cache() if force is True: # Force a regen of the entitlement certs for this consumer if not self.cp.regenEntitlementCertificates( self.identity.uuid, True): log.debug( "Warning: Unable to refresh entitlement certificates; service likely unavailable" ) return self.entcertlib.update()
class EntitlementService(object): def __init__(self, cp=None): self.cp = cp self.identity = inj.require(inj.IDENTITY) self.product_dir = inj.require(inj.PROD_DIR) self.entitlement_dir = inj.require(inj.ENT_DIR) self.entcertlib = EntCertActionInvoker() def get_status(self, on_date=None): sorter = inj.require(inj.CERT_SORTER, on_date) if self.identity.is_valid(): overall_status = sorter.get_system_status() reasons = sorter.reasons.get_name_message_map() valid = sorter.is_valid() return {'status': overall_status, 'reasons': reasons, 'valid': valid} else: return {'status': 'Unknown', 'reasons': {}, 'valid': False} def get_pools(self, pool_subsets=None, matches=None, pool_only=None, match_installed=None, no_overlap=None, service_level=None, show_all=None, on_date=None, future=None, after=None, **kwargs): # We accept a **kwargs argument so that the DBus object can pass whatever dictionary it receives # via keyword expansion. if kwargs: raise exceptions.ValidationError(_("Unknown arguments: %s") % kwargs.keys()) if isinstance(pool_subsets, six.string_types): pool_subsets = [pool_subsets] # [] or None means look at all pools if not pool_subsets: pool_subsets = ['installed', 'consumed', 'available'] options = { 'pool_subsets': pool_subsets, 'matches': matches, 'pool_only': pool_only, 'match_installed': match_installed, 'no_overlap': no_overlap, 'service_level': service_level, 'show_all': show_all, 'on_date': on_date, 'future': future, 'after': after, } self.validate_options(options) results = {} if 'installed' in pool_subsets: installed = products.InstalledProducts(self.cp).list(matches) results['installed'] = [x._asdict() for x in installed] if 'consumed' in pool_subsets: consumed = self.get_consumed_product_pools(service_level=service_level, matches=matches) if pool_only: results['consumed'] = [x._asdict()['pool_id'] for x in consumed] else: results['consumed'] = [x._asdict() for x in consumed] if 'available' in pool_subsets: available = self.get_available_pools( show_all=show_all, on_date=on_date, no_overlap=no_overlap, match_installed=match_installed, matches=matches, service_level=service_level, future=future, after=after, ) if pool_only: results['available'] = [x['id'] for x in available] else: results['available'] = available return results def get_consumed_product_pools(self, service_level=None, matches=None): # Use a named tuple so that the result can be unpacked into other functions ConsumedStatus = collections.namedtuple('ConsumedStatus', [ 'subscription_name', 'provides', 'sku', 'contract', 'account', 'serial', 'pool_id', 'provides_management', 'active', 'quantity_used', 'service_level', 'service_type', 'status_details', 'subscription_type', 'starts', 'ends', 'system_type', ]) sorter = inj.require(inj.CERT_SORTER) cert_reasons_map = sorter.reasons.get_subscription_reasons_map() pooltype_cache = inj.require(inj.POOLTYPE_CACHE) consumed_statuses = [] # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. When new certificate is installed/deleted and rhsm-service # is running, then list of certificate is not automatically refreshed ATM. self.entitlement_dir.refresh() certs = self.entitlement_dir.list() cert_filter = utils.EntitlementCertificateFilter(filter_string=matches, service_level=service_level) if service_level is not None or matches is not None: certs = list(filter(cert_filter.match, certs)) # Now we need to transform the EntitlementCertificate object into # something JSON-like for consumption for cert in certs: # for some certs, order can be empty # so we default the values and populate them if # they exist. BZ974587 name = "" sku = "" contract = "" account = "" quantity_used = "" service_level = "" service_type = "" system_type = "" provides_management = "No" order = cert.order if order: service_level = order.service_level or "" service_type = order.service_type or "" name = order.name sku = order.sku contract = order.contract or "" account = order.account or "" quantity_used = order.quantity_used if order.virt_only: system_type = _("Virtual") else: system_type = _("Physical") if order.provides_management: provides_management = _("Yes") else: provides_management = _("No") pool_id = _("Not Available") if hasattr(cert.pool, "id"): pool_id = cert.pool.id product_names = [p.name for p in cert.products] reasons = [] pool_type = '' if inj.require(inj.CERT_SORTER).are_reasons_supported(): if cert.subject and 'CN' in cert.subject: if cert.subject['CN'] in cert_reasons_map: reasons = cert_reasons_map[cert.subject['CN']] pool_type = pooltype_cache.get(pool_id) # 1180400: Status details is empty when GUI is not if not reasons: if cert in sorter.valid_entitlement_certs: reasons.append(_("Subscription is current")) else: if cert.valid_range.end() < datetime.datetime.now(certificate.GMT()): reasons.append(_("Subscription is expired")) else: reasons.append(_("Subscription has not begun")) else: reasons.append(_("Subscription management service doesn't support Status Details.")) consumed_statuses.append(ConsumedStatus( name, product_names, sku, contract, account, cert.serial, pool_id, provides_management, cert.is_valid(), quantity_used, service_level, service_type, reasons, pool_type, managerlib.format_date(cert.valid_range.begin()), managerlib.format_date(cert.valid_range.end()), system_type)) return consumed_statuses def get_available_pools(self, show_all=None, on_date=None, no_overlap=None, match_installed=None, matches=None, service_level=None, future=None, after=None): available_pools = managerlib.get_available_entitlements( get_all=show_all, active_on=on_date, overlapping=no_overlap, uninstalled=match_installed, filter_string=matches, future=future, after=after, ) def filter_pool_by_service_level(pool_data): pool_level = "" if pool_data['service_level']: pool_level = pool_data['service_level'] return service_level.lower() == pool_level.lower() if service_level is not None: available_pools = list(filter(filter_pool_by_service_level, available_pools)) return available_pools def validate_options(self, options): if not set(['installed', 'consumed', 'available']).issuperset(options['pool_subsets']): raise exceptions.ValidationError( _('Error: invalid listing type provided. Only "installed", ' '"consumed", or "available" are allowed') ) if options['show_all'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --all is only applicable with --available") ) elif options['on_date'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --ondate is only applicable with --available") ) elif options['service_level'] is not None \ and not set(['consumed', 'available']).intersection(options['pool_subsets']): raise exceptions.ValidationError( _("Error: --servicelevel is only applicable with --available or --consumed") ) elif options['match_installed'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --match-installed is only applicable with --available") ) elif options['no_overlap'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --no-overlap is only applicable with --available") ) elif options['pool_only'] \ and not set(['consumed', 'available']).intersection(options['pool_subsets']): raise exceptions.ValidationError( _("Error: --pool-only is only applicable with --available and/or --consumed") ) elif not self.identity.is_valid() and 'available' in options['pool_subsets']: raise exceptions.ValidationError(_("Error: this system is not registered")) def _unbind_ids(self, unbind_method, consumer_uuid, ids): """ Method for unbinding entitlements :param unbind_method: unbindByPoolId or unbindBySerial :param consumer_uuid: UUID of consumer :param ids: List of serials or pool_ids :return: Tuple of two lists containing unbinded and not-unbinded subscriptions """ success = [] failure = [] for id_ in ids: try: unbind_method(consumer_uuid, id_) success.append(id_) except connection.RestlibException as re: if re.code == 410: raise failure.append(id_) log.error(re) return success, failure def remove_all_entitlements(self): """ Try to remove all entilements :return: Result of REST API call """ response = self.cp.unbindAll(self.identity.uuid) self.entcertlib.update() return response def remove_entilements_by_pool_ids(self, pool_ids): """ Try to remove entitlements by pool IDs :param pool_ids: List of pool IDs :return: List of serial numbers of removed subscriptions """ removed_serials = [] _pool_ids = utils.unique_list_items(pool_ids) # Don't allow duplicates # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. I vote for i-notify to be used there somehow. self.entitlement_dir.refresh() pool_id_to_serials = self.entitlement_dir.list_serials_for_pool_ids(_pool_ids) removed_pools, unremoved_pools = self._unbind_ids(self.cp.unbindByPoolId, self.identity.uuid, _pool_ids) if removed_pools: for pool_id in removed_pools: removed_serials.extend(pool_id_to_serials[pool_id]) self.entcertlib.update() return removed_pools, unremoved_pools, removed_serials def remove_entitlements_by_serials(self, serials): """ Try to remove pools by Serial numbers :param serials: List of serial numbers :return: List of serial numbers of already removed subscriptions """ _serials = utils.unique_list_items(serials) # Don't allow duplicates removed_serials, unremoved_serials = self._unbind_ids(self.cp.unbindBySerial, self.identity.uuid, _serials) self.entcertlib.update() return removed_serials, unremoved_serials
def main(self, args=None): # TODO: For now, we disable the CLI entirely. We may want to allow some commands in the future. if rhsm.config.in_container(): system_exit( os.EX_CONFIG, _("subscription-manager is disabled when running inside a container. Please refer to your host system for subscription management.\n" )) config_changed = False # In testing we sometimes specify args, otherwise use the default: if not args: args = sys.argv[1:] (self.options, self.args) = self.parser.parse_known_args(args) # we dont need argv[0] in this list... self.args = self.args[1:] # check for unparsed arguments if self.args: for arg in self.args: print(_("cannot parse argument: {}").format(arg)) system_exit(os.EX_USAGE) if hasattr(self.options, "insecure") and self.options.insecure: conf["server"]["insecure"] = "1" config_changed = True if hasattr(self.options, "server_url") and self.options.server_url: try: (self.server_hostname, self.server_port, self.server_prefix) = parse_server_info( self.options.server_url, conf) except ServerUrlParseError as e: print(_("Error parsing serverurl:")) handle_exception("Error parsing serverurl:", e) conf["server"]["hostname"] = self.server_hostname conf["server"]["port"] = self.server_port conf["server"]["prefix"] = self.server_prefix if self.server_port: self.server_port = int(self.server_port) config_changed = True if hasattr(self.options, "base_url") and self.options.base_url: try: (baseurl_server_hostname, baseurl_server_port, baseurl_server_prefix) = parse_baseurl_info( self.options.base_url) except ServerUrlParseError as e: print(_("Error parsing baseurl:")) handle_exception("Error parsing baseurl:", e) conf["rhsm"]["baseurl"] = format_baseurl(baseurl_server_hostname, baseurl_server_port, baseurl_server_prefix) config_changed = True # support foo.example.com:3128 format if hasattr(self.options, "proxy_url") and self.options.proxy_url: parts = remove_scheme(self.options.proxy_url).split(':') self.proxy_hostname = parts[0] # no ':' if len(parts) > 1: self.proxy_port = int(parts[1]) else: # if no port specified, use the one from the config, or fallback to the default self.proxy_port = conf['server'].get_int( 'proxy_port') or rhsm.config.DEFAULT_PROXY_PORT config_changed = True if hasattr(self.options, "proxy_user") and self.options.proxy_user: self.proxy_user = self.options.proxy_user if hasattr(self.options, "proxy_password") and self.options.proxy_password: self.proxy_password = self.options.proxy_password if hasattr(self.options, "no_proxy") and self.options.no_proxy: self.no_proxy = self.options.no_proxy # Proxy information isn't written to the config, so we have to make sure # the sorter gets it connection_info = {} if self.proxy_hostname: connection_info['proxy_hostname_arg'] = self.proxy_hostname if self.proxy_port: connection_info['proxy_port_arg'] = self.proxy_port if self.proxy_user: connection_info['proxy_user_arg'] = self.proxy_user if self.proxy_password: connection_info['proxy_password_arg'] = self.proxy_password if self.server_hostname: connection_info['host'] = self.server_hostname if self.server_port: connection_info['ssl_port'] = self.server_port if self.server_prefix: connection_info['handler'] = self.server_prefix if self.no_proxy: connection_info['no_proxy_arg'] = self.no_proxy self.cp_provider = inj.require(inj.CP_PROVIDER) self.cp_provider.set_connection_info(**connection_info) self.log.debug("X-Correlation-ID: {id}".format(id=self.correlation_id)) self.cp_provider.set_correlation_id(self.correlation_id) self.log_client_version() if self.require_connection(): # make sure we pass in the new server info, otherwise we # we use the defaults from connection module init # we've set self.proxy* here, so we'll use them if they # are set self.cp = self.cp_provider.get_consumer_auth_cp() # no auth cp for get / (resources) and # get /status (status and versions) self.no_auth_cp = self.cp_provider.get_no_auth_cp() self.entcertlib = EntCertActionInvoker() if config_changed: try: # catch host/port issues; does not catch auth issues if not self.test_proxy_connection(): system_exit( os.EX_UNAVAILABLE, _("Proxy connection failed, please check your settings." )) # this tries to actually connect to the server and ping it if not is_valid_server_info(self.no_auth_cp): system_exit( os.EX_UNAVAILABLE, _("Unable to reach the server at {host}:{port}{handler}" ).format(host=self.no_auth_cp.host, port=self.no_auth_cp.ssl_port, handler=self.no_auth_cp.handler)) except MissingCaCertException: system_exit( os.EX_CONFIG, _("Error: CA certificate for subscription service has not been installed." )) except ProxyException: system_exit( os.EX_UNAVAILABLE, _("Proxy connection failed, please check your settings." )) else: self.cp = None # do the work, catch most common errors here: try: return_code = self._do_command() # Only persist the config changes if there was no exception if config_changed and self.persist_server_options(): conf.persist() if return_code is not None: return return_code except (CertificateException, ssl.SSLError) as e: log.error(e) system_exit(os.EX_SOFTWARE, _('System certificates corrupted. Please reregister.')) except connection.GoneException as ge: if ge.deleted_id == self.identity.uuid: log.critical( "Consumer profile \"{uuid}\" has been deleted from the server." .format(uuid=self.identity.uuid)) system_exit( os.EX_UNAVAILABLE, _("Consumer profile \"{uuid}\" has been deleted from the server. You can use command clean or unregister to remove local profile." ).format(uuid=self.identity.uuid)) else: raise ge except InvalidCLIOptionError as err: # This exception is handled in cli module raise err except Exception as err: handle_exception("exception caught in subscription-manager", err)
class EntitlementService(object): def __init__(self, cp=None): self.cp = cp self.identity = inj.require(inj.IDENTITY) self.product_dir = inj.require(inj.PROD_DIR) self.entitlement_dir = inj.require(inj.ENT_DIR) self.entcertlib = EntCertActionInvoker() @classmethod def parse_date(cls, on_date): """ Return new datetime parsed from date :param on_date: String representing date :return It returns datetime.datime structure representing date """ try: on_date = datetime.datetime.strptime(on_date, '%Y-%m-%d') except ValueError: raise ValueError( _("Date entered is invalid. Date should be in YYYY-MM-DD format (example: " ) + time.strftime("%Y-%m-%d", time.localtime()) + " )") if on_date.date() < datetime.datetime.now().date(): raise ValueError(_("Past dates are not allowed")) return on_date def get_status(self, on_date=None): sorter = inj.require(inj.CERT_SORTER, on_date) # When singleton CertSorter was created with different argument on_date, then # it is necessary to update corresponding attribute in object (dependency # injection doesn't do it automatically). if sorter.on_date != on_date: sorter.on_date = on_date # Force reload status from the server to be sure that we get valid status for new date. # It is necessary to do it for rhsm.service, because it can run for very long time without # restart. sorter.load() if self.identity.is_valid(): overall_status = sorter.get_system_status() reasons = sorter.reasons.get_name_message_map() valid = sorter.is_valid() return { 'status': overall_status, 'reasons': reasons, 'valid': valid } else: return {'status': 'Unknown', 'reasons': {}, 'valid': False} def get_pools(self, pool_subsets=None, matches=None, pool_only=None, match_installed=None, no_overlap=None, service_level=None, show_all=None, on_date=None, future=None, after_date=None, **kwargs): # We accept a **kwargs argument so that the DBus object can pass whatever dictionary it receives # via keyword expansion. if kwargs: raise exceptions.ValidationError( _("Unknown arguments: %s") % kwargs.keys()) if isinstance(pool_subsets, six.string_types): pool_subsets = [pool_subsets] # [] or None means look at all pools if not pool_subsets: pool_subsets = ['installed', 'consumed', 'available'] options = { 'pool_subsets': pool_subsets, 'matches': matches, 'pool_only': pool_only, 'match_installed': match_installed, 'no_overlap': no_overlap, 'service_level': service_level, 'show_all': show_all, 'on_date': on_date, 'future': future, 'after_date': after_date, } self.validate_options(options) results = {} if 'installed' in pool_subsets: installed = products.InstalledProducts(self.cp).list(matches) results['installed'] = [x._asdict() for x in installed] if 'consumed' in pool_subsets: consumed = self.get_consumed_product_pools( service_level=service_level, matches=matches) if pool_only: results['consumed'] = [ x._asdict()['pool_id'] for x in consumed ] else: results['consumed'] = [x._asdict() for x in consumed] if 'available' in pool_subsets: available = self.get_available_pools( show_all=show_all, on_date=on_date, no_overlap=no_overlap, match_installed=match_installed, matches=matches, service_level=service_level, future=future, after_date=after_date, ) if pool_only: results['available'] = [x['id'] for x in available] else: results['available'] = available return results def get_consumed_product_pools(self, service_level=None, matches=None): # Use a named tuple so that the result can be unpacked into other functions ConsumedStatus = collections.namedtuple('ConsumedStatus', [ 'subscription_name', 'provides', 'sku', 'contract', 'account', 'serial', 'pool_id', 'provides_management', 'active', 'quantity_used', 'service_level', 'service_type', 'status_details', 'subscription_type', 'starts', 'ends', 'system_type', ]) sorter = inj.require(inj.CERT_SORTER) cert_reasons_map = sorter.reasons.get_subscription_reasons_map() pooltype_cache = inj.require(inj.POOLTYPE_CACHE) consumed_statuses = [] # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. When new certificate is installed/deleted and rhsm-service # is running, then list of certificate is not automatically refreshed ATM. self.entitlement_dir.refresh() certs = self.entitlement_dir.list() cert_filter = utils.EntitlementCertificateFilter( filter_string=matches, service_level=service_level) if service_level is not None or matches is not None: certs = list(filter(cert_filter.match, certs)) # Now we need to transform the EntitlementCertificate object into # something JSON-like for consumption for cert in certs: # for some certs, order can be empty # so we default the values and populate them if # they exist. BZ974587 name = "" sku = "" contract = "" account = "" quantity_used = "" service_level = "" service_type = "" system_type = "" provides_management = "No" order = cert.order if order: service_level = order.service_level or "" service_type = order.service_type or "" name = order.name sku = order.sku contract = order.contract or "" account = order.account or "" quantity_used = order.quantity_used if order.virt_only: system_type = _("Virtual") else: system_type = _("Physical") if order.provides_management: provides_management = _("Yes") else: provides_management = _("No") pool_id = _("Not Available") if hasattr(cert.pool, "id"): pool_id = cert.pool.id product_names = [p.name for p in cert.products] reasons = [] pool_type = '' if inj.require(inj.CERT_SORTER).are_reasons_supported(): if cert.subject and 'CN' in cert.subject: if cert.subject['CN'] in cert_reasons_map: reasons = cert_reasons_map[cert.subject['CN']] pool_type = pooltype_cache.get(pool_id) # 1180400: Status details is empty when GUI is not if not reasons: if cert in sorter.valid_entitlement_certs: reasons.append(_("Subscription is current")) else: if cert.valid_range.end() < datetime.datetime.now( certificate.GMT()): reasons.append(_("Subscription is expired")) else: reasons.append(_("Subscription has not begun")) else: reasons.append( _("Subscription management service doesn't support Status Details." )) consumed_statuses.append( ConsumedStatus( name, product_names, sku, contract, account, cert.serial, pool_id, provides_management, cert.is_valid(), quantity_used, service_level, service_type, reasons, pool_type, managerlib.format_date(cert.valid_range.begin()), managerlib.format_date(cert.valid_range.end()), system_type)) return consumed_statuses def get_available_pools(self, show_all=None, on_date=None, no_overlap=None, match_installed=None, matches=None, service_level=None, future=None, after_date=None): available_pools = managerlib.get_available_entitlements( get_all=show_all, active_on=on_date, overlapping=no_overlap, uninstalled=match_installed, filter_string=matches, future=future, after_date=after_date, ) def filter_pool_by_service_level(pool_data): pool_level = "" if pool_data['service_level']: pool_level = pool_data['service_level'] return service_level.lower() == pool_level.lower() if service_level is not None: available_pools = list( filter(filter_pool_by_service_level, available_pools)) return available_pools def validate_options(self, options): if not set(['installed', 'consumed', 'available']).issuperset( options['pool_subsets']): raise exceptions.ValidationError( _('Error: invalid listing type provided. Only "installed", ' '"consumed", or "available" are allowed')) if options['show_all'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --all is only applicable with --available")) elif options['on_date'] and 'available' not in options['pool_subsets']: raise exceptions.ValidationError( _("Error: --ondate is only applicable with --available")) elif options['service_level'] is not None \ and not set(['consumed', 'available']).intersection(options['pool_subsets']): raise exceptions.ValidationError( _("Error: --servicelevel is only applicable with --available or --consumed" )) elif options['match_installed'] and 'available' not in options[ 'pool_subsets']: raise exceptions.ValidationError( _("Error: --match-installed is only applicable with --available" )) elif options['no_overlap'] and 'available' not in options[ 'pool_subsets']: raise exceptions.ValidationError( _("Error: --no-overlap is only applicable with --available")) elif options['pool_only'] \ and not set(['consumed', 'available']).intersection(options['pool_subsets']): raise exceptions.ValidationError( _("Error: --pool-only is only applicable with --available and/or --consumed" )) elif not self.identity.is_valid( ) and 'available' in options['pool_subsets']: raise exceptions.ValidationError( _("Error: this system is not registered")) def _unbind_ids(self, unbind_method, consumer_uuid, ids): """ Method for unbinding entitlements :param unbind_method: unbindByPoolId or unbindBySerial :param consumer_uuid: UUID of consumer :param ids: List of serials or pool_ids :return: Tuple of two lists containing unbinded and not-unbinded subscriptions """ success = [] failure = [] for id_ in ids: try: unbind_method(consumer_uuid, id_) success.append(id_) except connection.RestlibException as re: if re.code == 410: raise failure.append(id_) log.error(re) return success, failure def remove_all_entitlements(self): """ Try to remove all entilements :return: Result of REST API call """ response = self.cp.unbindAll(self.identity.uuid) self.entcertlib.update() return response def remove_entilements_by_pool_ids(self, pool_ids): """ Try to remove entitlements by pool IDs :param pool_ids: List of pool IDs :return: List of serial numbers of removed subscriptions """ removed_serials = [] _pool_ids = utils.unique_list_items(pool_ids) # Don't allow duplicates # FIXME: the cache of CertificateDirectory should be smart enough and refreshing # should not be necessary. I vote for i-notify to be used there somehow. self.entitlement_dir.refresh() pool_id_to_serials = self.entitlement_dir.list_serials_for_pool_ids( _pool_ids) removed_pools, unremoved_pools = self._unbind_ids( self.cp.unbindByPoolId, self.identity.uuid, _pool_ids) if removed_pools: for pool_id in removed_pools: removed_serials.extend(pool_id_to_serials[pool_id]) self.entcertlib.update() return removed_pools, unremoved_pools, removed_serials def remove_entitlements_by_serials(self, serials): """ Try to remove pools by Serial numbers :param serials: List of serial numbers :return: List of serial numbers of already removed subscriptions """ _serials = utils.unique_list_items(serials) # Don't allow duplicates removed_serials, unremoved_serials = self._unbind_ids( self.cp.unbindBySerial, self.identity.uuid, _serials) self.entcertlib.update() return removed_serials, unremoved_serials def reload(self): sorter = inj.require(inj.CERT_SORTER, on_date=None) sorter.load()