def render(self, session, logger, cluster, keepbindings, **arguments): dbcluster = Cluster.get_unique(session, cluster, compel=True) if not dbcluster.personality.archetype.is_compileable: raise ArgumentError( "{0} is not a compilable archetype " "({1!s}).".format(dbcluster, dbcluster.personality.archetype) ) chooser = Chooser(dbcluster, logger=logger, required_only=not keepbindings) chooser.set_required() chooser.flush_changes() td = TemplateDomain(dbcluster.branch, dbcluster.sandbox_author, logger=logger) # Force a domain lock as pan might overwrite any of the profiles... with chooser.get_key(): try: chooser.write_plenary_templates(locked=True) td.compile(session, only=chooser.plenaries.object_templates, locked=True) except: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise return
def render(self, session, logger, hostname, service, instance, force=False, **arguments): dbhost = hostname_to_host(session, hostname) dbservice = Service.get_unique(session, service, compel=True) chooser = Chooser(dbhost, logger=logger, required_only=False) if instance: dbinstance = get_service_instance(session, dbservice, instance) chooser.set_single(dbservice, dbinstance, force=force) else: chooser.set_single(dbservice, force=force) chooser.flush_changes() chooser.write_plenary_templates() return
def compile(self, session, dbhost, logger, keepbindings): chooser = Chooser(dbhost, logger=logger, required_only=not keepbindings) chooser.set_required() chooser.flush_changes() td = TemplateDomain(dbhost.branch, dbhost.sandbox_author, logger=logger) with chooser.get_key(): try: chooser.write_plenary_templates(locked=True) td.compile(session, only=chooser.plenaries.object_templates, locked=True) except: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise return
def render(self, session, logger, cluster, keepbindings, **arguments): dbcluster = Cluster.get_unique(session, cluster, compel=True) if not dbcluster.personality.archetype.is_compileable: raise ArgumentError("{0} is not a compilable archetype " "({1!s}).".format(dbcluster, dbcluster.personality.archetype)) chooser = Chooser(dbcluster, logger=logger, required_only=not(keepbindings)) chooser.set_required() chooser.flush_changes() # Force a domain lock as pan might overwrite any of the profiles... key = CompileKey.merge([chooser.get_write_key(), CompileKey(domain=dbcluster.branch.name, logger=logger)]) try: lock_queue.acquire(key) chooser.write_plenary_templates(locked=True) profile_list = add_cluster_data(dbcluster) profile_list.extend(chooser.changed_server_fqdns()) td = TemplateDomain(dbcluster.branch, dbcluster.sandbox_author, logger=logger) td.compile(session, only=profile_list, locked=True) except: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise finally: lock_queue.release(key) return
def render(self, session, logger, hostname, cluster, personality, **arguments): dbhost = hostname_to_host(session, hostname) dbcluster = Cluster.get_unique(session, cluster, compel=True) if dbcluster.status.name == 'decommissioned': raise ArgumentError("Cannot add hosts to decommissioned clusters.") # We only support changing personality within the same # archetype. The archetype decides things like which OS, how # it builds (dhcp, etc), whether it's compilable, and # switching all of that by side-effect seems wrong # somehow. And besides, it would make the user-interface and # implementation for this command ugly in order to support # changing all of those options. personality_change = False if personality is not None: dbpersonality = Personality.get_unique(session, name=personality, archetype=dbhost.archetype, compel=True) if dbhost.personality != dbpersonality: dbhost.personality = dbpersonality personality_change = True # Allow for non-restricted clusters (the default?) if (len(dbcluster.allowed_personalities) > 0 and dbhost.personality not in dbcluster.allowed_personalities): raise ArgumentError("The personality %s for %s is not allowed " "by the cluster. Specify --personality " "and provide one of %s" % (dbhost.personality, dbhost.fqdn, ", ".join([x.name for x in dbcluster.allowed_personalities]))) # Now that we've changed the personality, we can check # if this is a valid membership change dbcluster.validate_membership(dbhost) plenaries = PlenaryCollection(logger=logger) plenaries.append(Plenary.get_plenary(dbcluster)) if dbhost.cluster and dbhost.cluster != dbcluster: logger.client_info("Removing {0:l} from {1:l}.".format(dbhost, dbhost.cluster)) old_cluster = dbhost.cluster old_cluster.hosts.remove(dbhost) remove_service_addresses(old_cluster, dbhost) old_cluster.validate() session.expire(dbhost, ['_cluster']) plenaries.append(Plenary.get_plenary(old_cluster)) # Apply the service addresses to the new member for res in walk_resources(dbcluster): if not isinstance(res, ServiceAddress): continue apply_service_address(dbhost, res.interfaces, res, logger) if dbhost.cluster: if personality_change: raise ArgumentError("{0:l} already in {1:l}, use " "aq reconfigure to change personality." .format(dbhost, dbhost.cluster)) # the cluster has not changed, therefore there's nothing # to do here. return # Calculate the node index: build a map of all possible values, remove # the used ones, and pick the smallest remaining one node_index_map = set(xrange(len(dbcluster._hosts) + 1)) for link in dbcluster._hosts: # The cluster may have been bigger in the past, so node indexes may # be larger than the current cluster size try: node_index_map.remove(link.node_index) except KeyError: pass dbcluster.hosts.append((dbhost, min(node_index_map))) dbcluster.validate() # demote a host when switching clusters # promote a host when switching clusters if dbhost.status.name == 'ready': if dbcluster.status.name != 'ready': dbalmost = HostAlmostready.get_instance(session) dbhost.status.transition(dbhost, dbalmost) plenaries.append(Plenary.get_plenary(dbhost)) elif dbhost.status.name == 'almostready': if dbcluster.status.name == 'ready': dbready = HostReady.get_instance(session) dbhost.status.transition(dbhost, dbready) plenaries.append(Plenary.get_plenary(dbhost)) session.flush() # Enforce that service instances are set correctly for the # new cluster association. chooser = Chooser(dbhost, logger=logger) chooser.set_required() chooser.flush_changes() # the chooser will include the host plenary with CompileKey.merge([chooser.get_key(), plenaries.get_key()]): plenaries.stash() try: chooser.write_plenary_templates(locked=True) plenaries.write(locked=True) except: chooser.restore_stash() plenaries.restore_stash() raise return
def render(self, session, logger, hostname, cluster, personality, **arguments): dbhost = hostname_to_host(session, hostname) dbcluster = Cluster.get_unique(session, cluster, compel=True) if dbcluster.status.name == 'decommissioned': raise ArgumentError("Cannot add hosts to decommissioned clusters.") # We only support changing personality within the same # archetype. The archetype decides things like which OS, how # it builds (dhcp, etc), whether it's compilable, and # switching all of that by side-effect seems wrong # somehow. And besides, it would make the user-interface and # implementation for this command ugly in order to support # changing all of those options. personality_change = False if personality is not None: dbpersonality = Personality.get_unique(session, name=personality, archetype=dbhost.archetype, compel=True) if dbhost.personality != dbpersonality: dbhost.personality = dbpersonality personality_change = True # Allow for non-restricted clusters (the default?) if (len(dbcluster.allowed_personalities) > 0 and dbhost.personality not in dbcluster.allowed_personalities): raise ArgumentError( "The personality %s for %s is not allowed " "by the cluster. Specify --personality " "and provide one of %s" % (dbhost.personality, dbhost.fqdn, ", ".join( [x.name for x in dbcluster.allowed_personalities]))) # Now that we've changed the personality, we can check # if this is a valid membership change dbcluster.validate_membership(dbhost) plenaries = PlenaryCollection(logger=logger) plenaries.append(Plenary.get_plenary(dbcluster)) if dbhost.cluster and dbhost.cluster != dbcluster: logger.client_info("Removing {0:l} from {1:l}.".format( dbhost, dbhost.cluster)) old_cluster = dbhost.cluster old_cluster.hosts.remove(dbhost) remove_service_addresses(old_cluster, dbhost) old_cluster.validate() session.expire(dbhost, ['_cluster']) plenaries.append(Plenary.get_plenary(old_cluster)) # Apply the service addresses to the new member for res in walk_resources(dbcluster): if not isinstance(res, ServiceAddress): continue apply_service_address(dbhost, res.interfaces, res) if dbhost.cluster: if personality_change: raise ArgumentError( "{0:l} already in {1:l}, use " "aq reconfigure to change personality.".format( dbhost, dbhost.cluster)) # the cluster has not changed, therefore there's nothing # to do here. return # Calculate the node index: build a map of all possible values, remove # the used ones, and pick the smallest remaining one node_index_map = set(xrange(len(dbcluster._hosts) + 1)) for link in dbcluster._hosts: # The cluster may have been bigger in the past, so node indexes may # be larger than the current cluster size try: node_index_map.remove(link.node_index) except KeyError: pass dbcluster.hosts.append((dbhost, min(node_index_map))) dbcluster.validate() # demote a host when switching clusters # promote a host when switching clusters if dbhost.status.name == 'ready': if dbcluster.status.name != 'ready': dbalmost = HostLifecycle.get_unique(session, 'almostready', compel=True) dbhost.status.transition(dbhost, dbalmost) plenaries.append(Plenary.get_plenary(dbhost)) elif dbhost.status.name == 'almostready': if dbcluster.status.name == 'ready': dbready = HostLifecycle.get_unique(session, 'ready', compel=True) dbhost.status.transition(dbhost, dbready) plenaries.append(Plenary.get_plenary(dbhost)) session.flush() # Enforce that service instances are set correctly for the # new cluster association. chooser = Chooser(dbhost, logger=logger) chooser.set_required() chooser.flush_changes() # the chooser will include the host plenary key = CompileKey.merge( [chooser.get_write_key(), plenaries.get_write_key()]) try: lock_queue.acquire(key) chooser.write_plenary_templates(locked=True) plenaries.write(locked=True) except: chooser.restore_stash() plenaries.restore_stash() raise finally: lock_queue.release(key) return
def compile(self, session, dbhost, logger, keepbindings): chooser = Chooser(dbhost, logger=logger, required_only=not (keepbindings)) chooser.set_required() chooser.flush_changes() hosts = chooser.changed_server_fqdns() hosts.add(dbhost.fqdn) # Force a host lock as pan might overwrite the profile... key = chooser.get_write_key() for fqdn in hosts: key = CompileKey.merge([ key, CompileKey(domain=dbhost.branch.name, profile=fqdn, logger=logger) ]) try: lock_queue.acquire(key) chooser.write_plenary_templates(locked=True) td = TemplateDomain(dbhost.branch, dbhost.sandbox_author, logger=logger) td.compile(session, only=hosts, locked=True) except: if chooser: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise finally: lock_queue.release(key) return
def compile(self, session, dbhost, logger, keepbindings): chooser = Chooser(dbhost, logger=logger, required_only=not(keepbindings)) chooser.set_required() chooser.flush_changes() hosts = chooser.changed_server_fqdns() hosts.add(dbhost.fqdn) # Force a host lock as pan might overwrite the profile... key = chooser.get_write_key() for fqdn in hosts: key = CompileKey.merge([key, CompileKey(domain=dbhost.branch.name, profile=fqdn, logger=logger)]) try: lock_queue.acquire(key) chooser.write_plenary_templates(locked=True) td = TemplateDomain(dbhost.branch, dbhost.sandbox_author, logger=logger) td.compile(session, only=hosts, locked=True) except: if chooser: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise finally: lock_queue.release(key) return
def reconfigure_list(self, session, logger, dbhosts, archetype, personality, buildstatus, osname, osversion, **arguments): failed = [] # Check all the parameters up front. # Some of these could be more intelligent about defaults # (either by checking for unique entries or relying on the list) # - starting simple. if archetype: dbarchetype = Archetype.get_unique(session, archetype, compel=True) if dbarchetype.cluster_type is not None: raise ArgumentError("Archetype %s is a cluster archetype" % dbarchetype.name) # TODO: Once OS is a first class object this block needs # to check that either OS is also being reset or that the # OS is valid for the new archetype. else: dbarchetype = None if personality: dbpersonality = Personality.get_unique(session, name=personality, archetype=dbarchetype, compel=True) if osname and not osversion: raise ArgumentError("Please specify --osversion for OS %s." % osname) if osversion: if not osname: raise ArgumentError("Please specify --osname to use with " "OS version %s." % osversion) # Linux model names are the same under aurora and aquilon, so # allowing to omit --archetype would not be useful if not archetype: raise ArgumentError("Please specify --archetype for OS " "%s, version %s." % (osname, osversion)) dbos = OperatingSystem.get_unique(session, name=osname, version=osversion, archetype=dbarchetype, compel=True) else: dbos = None if buildstatus: dbstatus = HostLifecycle.get_unique(session, buildstatus, compel=True) # Take a shortcut if there's nothing to do, but only after all the other # parameters have been checked if not dbhosts: return personalities = {} branches = {} authors = {} # Do any final cross-list or dependency checks before entering # the Chooser loop. for dbhost in dbhosts: if dbhost.branch in branches: branches[dbhost.branch].append(dbhost) else: branches[dbhost.branch] = [dbhost] if dbhost.sandbox_author in authors: authors[dbhost.sandbox_author].append(dbhost) else: authors[dbhost.sandbox_author] = [dbhost] if dbos and not dbarchetype and dbhost.archetype != dbos.archetype: failed.append("{0}: Cannot change operating system because it " "needs {1:l} instead of " "{2:l}.".format(dbhost.fqdn, dbhost.archetype, dbos.archetype)) if dbarchetype and not dbos and \ dbhost.operating_system.archetype != dbarchetype: failed.append("{0}: Cannot change archetype because {1:l} needs " "{2:l}.".format(dbhost.fqdn, dbhost.operating_system, dbhost.operating_system.archetype)) if (personality and dbhost.cluster and len(dbhost.cluster.allowed_personalities) > 0 and dbpersonality not in dbhost.cluster.allowed_personalities): allowed = ["%s/%s" % (p.archetype.name, p.name) for p in dbhost.cluster.allowed_personalities] failed.append("{0}: The {1:l} is not allowed by {2}. " "Specify one of {3}.".format( dbhost.fqdn, dbpersonality, dbhost.cluster, allowed)) if personality: personalities[dbhost.fqdn] = dbpersonality elif archetype: personalities[dbhost.fqdn] = Personality.get_unique(session, name=dbhost.personality.name, archetype=dbarchetype) if not personalities[dbhost.fqdn]: failed.append("%s: No personality %s found for archetype " "%s." % (dbhost.fqdn, dbhost.personality.name, dbarchetype.name)) if failed: raise ArgumentError("Cannot modify the following hosts:\n%s" % "\n".join(failed)) if len(branches) > 1: keys = branches.keys() branch_sort = lambda x, y: cmp(len(branches[x]), len(branches[y])) keys.sort(cmp=branch_sort) stats = ["{0:d} hosts in {1:l}".format(len(branches[branch]), branch) for branch in keys] raise ArgumentError("All hosts must be in the same domain or " "sandbox:\n%s" % "\n".join(stats)) dbbranch = branches.keys()[0] if len(authors) > 1: keys = authors.keys() author_sort = lambda x, y: cmp(len(authors[x]), len(authors[y])) keys.sort(cmp=author_sort) stats = ["%s hosts with sandbox author %s" % (len(authors[author]), author.name) for author in keys] raise ArgumentError("All hosts must be managed by the same " "sandbox author:\n%s" % "\n".join(stats)) dbauthor = authors.keys()[0] failed = [] choosers = [] for dbhost in dbhosts: if dbhost.fqdn in personalities: dbhost.personality = personalities[dbhost.fqdn] session.add(dbhost) if osversion: dbhost.operating_system = dbos session.add(dbhost) if buildstatus: dbhost.status.transition(dbhost, dbstatus) session.add(dbhost) session.flush() logger.client_info("Verifying service bindings.") for dbhost in dbhosts: if dbhost.archetype.is_compileable: if arguments.get("keepbindings", None): chooser = Chooser(dbhost, logger=logger, required_only=False) else: chooser = Chooser(dbhost, logger=logger, required_only=True) choosers.append(chooser) try: chooser.set_required() except ArgumentError, e: failed.append(str(e))
def render(self, session, logger, cluster, keepbindings, **arguments): dbcluster = Cluster.get_unique(session, cluster, compel=True) if not dbcluster.personality.archetype.is_compileable: raise ArgumentError("{0} is not a compilable archetype " "({1!s}).".format( dbcluster, dbcluster.personality.archetype)) chooser = Chooser(dbcluster, logger=logger, required_only=not (keepbindings)) chooser.set_required() chooser.flush_changes() # Force a domain lock as pan might overwrite any of the profiles... key = CompileKey.merge([ chooser.get_write_key(), CompileKey(domain=dbcluster.branch.name, logger=logger) ]) try: lock_queue.acquire(key) chooser.write_plenary_templates(locked=True) profile_list = add_cluster_data(dbcluster) profile_list.extend(chooser.changed_server_fqdns()) td = TemplateDomain(dbcluster.branch, dbcluster.sandbox_author, logger=logger) td.compile(session, only=profile_list, locked=True) except: chooser.restore_stash() # Okay, cleaned up templates, make sure the caller knows # we've aborted so that DB can be appropriately rollback'd. raise finally: lock_queue.release(key) return