def render(self, session, dbuser, fqdn, building, ip, network_environment, comments, **arguments): dbnet_env = NetworkEnvironment.get_unique_or_default( session, network_environment) self.az.check_network_environment(dbuser, dbnet_env) if building: dbbuilding = Building.get_unique(session, building, compel=True) else: dbbuilding = None (short, dbdns_domain) = parse_fqdn(session, fqdn) dbfqdn = Fqdn.get_or_create(session, name=short, dns_domain=dbdns_domain, dns_environment=dbnet_env.dns_environment) if ip: dbnetwork = get_net_id_from_ip(session, ip, dbnet_env) dbdns_rec = ARecord.get_or_create(session, fqdn=dbfqdn, ip=ip, network=dbnetwork) else: dbdns_rec = ARecord.get_unique(session, dbfqdn, compel=True) ip = dbdns_rec.ip dbnetwork = dbdns_rec.network assert ip in dbnetwork.network, "IP %s is outside network %s" % ( ip, dbnetwork.ip) if ip in dbnetwork.router_ips: raise ArgumentError( "IP address {0} is already present as a router " "for {1:l}.".format(ip, dbnetwork)) # Policy checks are valid only for internal networks if dbnetwork.is_internal: if ip >= dbnetwork.first_usable_host or \ int(ip) - int(dbnetwork.ip) in dbnetwork.reserved_offsets: raise ArgumentError( "IP address {0} is not a valid router address " "on {1:l}.".format(ip, dbnetwork)) dbnetwork.routers.append( RouterAddress(ip=ip, location=dbbuilding, dns_environment=dbdns_rec.fqdn.dns_environment, comments=comments)) session.flush() # TODO: update the templates of Zebra hosts on the network return
def render(self, session, dbuser, ip, fqdn, network_environment, **arguments): dbnet_env = NetworkEnvironment.get_unique_or_default(session, network_environment) self.az.check_network_environment(dbuser, dbnet_env) if fqdn: dbdns_rec = ARecord.get_unique(session, fqdn=fqdn, dns_environment=dbnet_env.dns_environment, compel=True) ip = dbdns_rec.ip elif not ip: raise ArgumentError("Please specify either --ip or --fqdn.") dbnetwork = get_net_id_from_ip(session, ip, dbnet_env) dbrouter = None for rtaddr in dbnetwork.routers: if rtaddr.ip == ip: dbrouter = rtaddr break if not dbrouter: raise NotFoundException("IP address {0} is not a router on " "{1:l}.".format(ip, dbnetwork)) map(delete_dns_record, dbrouter.dns_records) dbnetwork.routers.remove(dbrouter) session.flush() # TODO: update the templates of Zebra hosts on the network return
def render(self, session, logger, auxiliary, **arguments): # Check dependencies, translate into user-friendly message dbauxiliary = ARecord.get_unique(session, fqdn=auxiliary, compel=True) is_aux = True if not dbauxiliary.assignments or len(dbauxiliary.assignments) > 1: is_aux = False else: assignment = dbauxiliary.assignments[0] dbinterface = assignment.interface if assignment.ip == dbinterface.hardware_entity.primary_ip: is_aux = False if assignment.interface.interface_type == 'management': is_aux = False if not is_aux: raise ArgumentError("{0:a} is not an auxiliary.".format(dbauxiliary)) return super(CommandDelAuxiliary, self).render(session, logger, machine=dbinterface.hardware_entity.label, chassis=None, network_device=None, interface=dbinterface.name, fqdn=auxiliary, ip=assignment.ip, label=None, keep_dns=False, network_environment=None, **arguments)
def render(self, session, ip, fqdn, all, network_environment, **arguments): dbnet_env = NetworkEnvironment.get_unique_or_default(session, network_environment) q = session.query(RouterAddress) q = q.join(Network) q = q.filter_by(network_environment=dbnet_env) q = q.options(contains_eager('network')) q = q.reset_joinpoint() q = q.options(undefer(RouterAddress.comments)) q = q.options(joinedload('location')) q = q.options(joinedload('dns_records')) if all: return q.all() if fqdn: dbdns_rec = ARecord.get_unique(session, fqdn=fqdn, compel=True) ip = dbdns_rec.ip errmsg = "named %s" % fqdn elif ip: errmsg = "with IP address %s" % ip else: raise ArgumentError("Please specify either --ip or --fqdn.") q = q.filter(RouterAddress.ip == ip) try: return q.one() except NoResultFound: raise NotFoundException("Router %s not found." % errmsg)
def render(self, session, ip, fqdn, all, network_environment, **arguments): dbnet_env = NetworkEnvironment.get_unique_or_default( session, network_environment) q = session.query(RouterAddress) q = q.join(Network) q = q.filter_by(network_environment=dbnet_env) q = q.options(contains_eager('network')) q = q.reset_joinpoint() q = q.options(undefer(RouterAddress.comments)) q = q.options(joinedload('location')) q = q.options(joinedload('dns_records')) if all: return q.all() if fqdn: dbdns_rec = ARecord.get_unique(session, fqdn=fqdn, compel=True) ip = dbdns_rec.ip errmsg = "named %s" % fqdn elif ip: errmsg = "with IP address %s" % ip else: raise ArgumentError("Please specify either --ip or --fqdn.") q = q.filter(RouterAddress.ip == ip) try: return q.one() except NoResultFound: raise NotFoundException("Router %s not found." % errmsg)
def render(self, session, dbuser, fqdn, building, ip, network_environment, comments, **arguments): dbnet_env = NetworkEnvironment.get_unique_or_default(session, network_environment) self.az.check_network_environment(dbuser, dbnet_env) if building: dbbuilding = Building.get_unique(session, building, compel=True) else: dbbuilding = None (short, dbdns_domain) = parse_fqdn(session, fqdn) dbfqdn = Fqdn.get_or_create(session, name=short, dns_domain=dbdns_domain, dns_environment=dbnet_env.dns_environment) if ip: dbnetwork = get_net_id_from_ip(session, ip, dbnet_env) dbdns_rec = ARecord.get_or_create(session, fqdn=dbfqdn, ip=ip, network=dbnetwork) else: dbdns_rec = ARecord.get_unique(session, dbfqdn, compel=True) ip = dbdns_rec.ip dbnetwork = dbdns_rec.network assert ip in dbnetwork.network, "IP %s is outside network %s" % (ip, dbnetwork.ip) if ip in dbnetwork.router_ips: raise ArgumentError("IP address {0} is already present as a router " "for {1:l}.".format(ip, dbnetwork)) # Policy checks are valid only for internal networks if dbnetwork.is_internal: if ip >= dbnetwork.first_usable_host or \ int(ip) - int(dbnetwork.ip) in dbnetwork.reserved_offsets: raise ArgumentError("IP address {0} is not a valid router address " "on {1:l}.".format(ip, dbnetwork)) dbnetwork.routers.append(RouterAddress(ip=ip, location=dbbuilding, dns_environment=dbdns_rec.fqdn.dns_environment, comments=comments)) session.flush() # TODO: update the templates of Zebra hosts on the network return
def render(self, session, fqdn, dns_domain, **arguments): dbdns_domain = DnsDomain.get_unique(session, dns_domain, compel=True) dba_rec = ARecord.get_unique(session, fqdn=fqdn, compel=True) ns_record = NsRecord.get_unique(session, dns_domain=dbdns_domain, a_record=dba_rec, compel=True) session.delete(ns_record) return
def render(self, session, auxiliary, **kwargs): dbdns_rec = ARecord.get_unique(session, fqdn=auxiliary, compel=True) if not dbdns_rec.assignments: raise ArgumentError("Address {0:a} is not assigned to any " "interfaces.".format(dbdns_rec)) hws = [] for addr in dbdns_rec.assignments: iface = addr.interface if iface.interface_type != 'public': raise ArgumentError("{0:a} is not an auxiliary.".format(dbdns_rec)) hws.append(iface.hardware_entity) return hws
def render(self, session, dns_domain, **kw): dbdns = DnsDomain.get_unique(session, dns_domain, compel=True) q = session.query(NsRecord).filter_by(dns_domain=dbdns) dba_record = ARecord.get_unique(session, fqdn=kw['fqdn'], compel=True) q = q.filter_by(a_record=dba_record) ns_rec = q.all() if not ns_rec: raise NotFoundException( "Could not find a dns_record for domain '%s'." % dns_domain) return ns_rec
def render(self, session, logger, auxiliary, **arguments): dbmachine = None with DeleteKey("system", logger=logger): # Check dependencies, translate into user-friendly message dbauxiliary = ARecord.get_unique(session, fqdn=auxiliary, compel=True) is_aux = True if not dbauxiliary.assignments or len(dbauxiliary.assignments) > 1: is_aux = False else: assignment = dbauxiliary.assignments[0] dbmachine = assignment.interface.hardware_entity if assignment.ip == dbmachine.primary_ip: is_aux = False if assignment.interface.interface_type == 'management': is_aux = False if not is_aux: raise ArgumentError( "{0:a} is not an auxiliary.".format(dbauxiliary)) # FIXME: Look for dependencies... oldinfo = DSDBRunner.snapshot_hw(dbmachine) session.delete(assignment) delete_dns_record(dbauxiliary) session.flush() session.expire(dbmachine) dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbmachine, oldinfo) dsdb_runner.commit_or_rollback( "Could not remove host %s from DSDB" % auxiliary) # Past the point of no return here (DSDB has been updated)... # probably not much of an issue if writing the plenary failed. # Commit the session so that we can free the delete lock. session.commit() if dbmachine: plenary_info = PlenaryMachineInfo(dbmachine, logger=logger) # This may create a new lock, so we free first above. plenary_info.write() if dbmachine.host: # FIXME: Reconfigure pass return
def render(self, session, auxiliary, **kwargs): dbdns_rec = ARecord.get_unique(session, fqdn=auxiliary, compel=True) if not dbdns_rec.assignments: raise ArgumentError("Address {0:a} is not assigned to any " "interfaces.".format(dbdns_rec)) hws = [] for addr in dbdns_rec.assignments: iface = addr.interface if iface.interface_type != 'public': raise ArgumentError( "{0:a} is not an auxiliary.".format(dbdns_rec)) hws.append(iface.hardware_entity) return hws
def render(self, session, fqdn, dns_domain, comments, **arguments): dbdns_domain = DnsDomain.get_unique(session, dns_domain, compel=True) dba_rec = ARecord.get_unique(session, fqdn=fqdn, compel=True) NsRecord.get_unique(session, a_record=dba_rec, dns_domain=dbdns_domain, preclude=True) ns_record = NsRecord(a_record=dba_rec, dns_domain=dbdns_domain, comments=comments) session.add(ns_record) return
def add_arecord(ip): """ adding a valid ARecord """ (ms, intrnl) = get_reqs() a_rcrd = ARecord(name=AREC_PREFIX + str(unique_number.next()), dns_domain=ms, dns_environment=intrnl, ip=ip, comments='comment here', session=sess) create(sess, a_rcrd) assert a_rcrd, 'no a_record created by %s' % func_name() sess.refresh(a_rcrd) return a_rcrd
def setup(): dmn = DnsDomain(name=DNS_DOMAIN_NAME) create(sess, dmn) assert dmn, 'no dns domain in %s' % func_name() pi = Building.get_unique(sess, name='pi', compel=True) n = IPv4Network(TEST_NET) net = Network(name=TEST_NET_NAME, network=n, location=pi) create(sess, net) assert net, 'no network created by %s' % func_name() ip = IPv4Address(TEST_IP) arec = ARecord(name=AREC_NAME, dns_domain=dmn, ip=ip, network=net) create(sess, arec) assert arec, 'no ARecord created by %s' % func_name()
def render(self, session, logger, auxiliary, **arguments): dbmachine = None with DeleteKey("system", logger=logger): # Check dependencies, translate into user-friendly message dbauxiliary = ARecord.get_unique(session, fqdn=auxiliary, compel=True) is_aux = True if not dbauxiliary.assignments or len(dbauxiliary.assignments) > 1: is_aux = False else: assignment = dbauxiliary.assignments[0] dbmachine = assignment.interface.hardware_entity if assignment.ip == dbmachine.primary_ip: is_aux = False if assignment.interface.interface_type == "management": is_aux = False if not is_aux: raise ArgumentError("{0:a} is not an auxiliary.".format(dbauxiliary)) # FIXME: Look for dependencies... oldinfo = DSDBRunner.snapshot_hw(dbmachine) session.delete(assignment) delete_dns_record(dbauxiliary) session.flush() session.expire(dbmachine) dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbmachine, oldinfo) dsdb_runner.commit_or_rollback("Could not remove host %s from DSDB" % auxiliary) # Past the point of no return here (DSDB has been updated)... # probably not much of an issue if writing the plenary failed. # Commit the session so that we can free the delete lock. session.commit() if dbmachine: plenary_info = PlenaryMachineInfo(dbmachine, logger=logger) # This may create a new lock, so we free first above. plenary_info.write() if dbmachine.host: # FIXME: Reconfigure pass return
def render(self, session, fqdn, ip, building, comments, **arguments): if fqdn: dbdns_rec = ARecord.get_unique(session, fqdn, compel=True) ip = dbdns_rec.ip if not ip: raise ArgumentError("Please specify either --ip or --fqdn.") router = RouterAddress.get_unique(session, ip=ip, compel=True) if building: dbbuilding = Building.get_unique(session, name=building, compel=True) router.location = dbbuilding if comments: router.comments = comments session.flush()
def convert_reserved_to_arecord(session, dbdns_rec, dbnetwork, ip): comments = dbdns_rec.comments dbhw_ent = dbdns_rec.hardware_entity dbfqdn = dbdns_rec.fqdn session.delete(dbdns_rec) session.flush() session.expire(dbhw_ent, ['primary_name']) session.expire(dbfqdn, ['dns_records']) dbdns_rec = ARecord(fqdn=dbfqdn, ip=ip, network=dbnetwork, comments=comments) session.add(dbdns_rec) if dbhw_ent: dbhw_ent.primary_name = dbdns_rec return dbdns_rec
def render(self, session, logger, fqdn, ip, reverse_ptr, dns_environment, network_environment, comments, **arguments): dbnet_env, dbdns_env = get_net_dns_env(session, network_environment, dns_environment) dbdns_rec = ARecord.get_unique(session, fqdn=fqdn, dns_environment=dbdns_env, compel=True) old_ip = dbdns_rec.ip old_comments = dbdns_rec.comments if ip: if dbdns_rec.hardware_entity: raise ArgumentError("{0} is a primary name, and its IP address " "cannot be changed.".format(dbdns_rec)) if dbdns_rec.assignments: ifaces = ", ".join(["%s/%s" % (addr.interface.hardware_entity, addr.interface) for addr in dbdns_rec.assignments]) raise ArgumentError("{0} is already used by the following " "interfaces, and its IP address cannot be " "changed: {1!s}." .format(dbdns_rec, ifaces)) dbnetwork = get_net_id_from_ip(session, ip, dbnet_env) q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) q = q.join(ARecord.fqdn) q = q.filter_by(dns_environment=dbdns_env) existing = q.first() if existing: raise ArgumentError("IP address {0!s} is already used by " "{1:l}." .format(ip, existing)) dbdns_rec.network = dbnetwork old_ip = dbdns_rec.ip dbdns_rec.ip = ip if reverse_ptr: old_reverse = dbdns_rec.reverse_ptr set_reverse_ptr(session, logger, dbdns_rec, reverse_ptr) if old_reverse and old_reverse != dbdns_rec.reverse_ptr: delete_target_if_needed(session, old_reverse) if comments: dbdns_rec.comments = comments session.flush() if dbdns_env.is_default and (dbdns_rec.ip != old_ip or dbdns_rec.comments != old_comments): dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host_details(dbdns_rec.fqdn, new_ip=dbdns_rec.ip, old_ip=old_ip, new_comments=dbdns_rec.comments, old_comments=old_comments) dsdb_runner.commit_or_rollback() return
def render( self, session, logger, machine, chassis, switch, interface, fqdn, ip, label, keep_dns, network_environment, **kwargs ): if machine: hwtype = "machine" hwname = machine elif chassis: hwtype = "chassis" hwname = chassis elif switch: hwtype = "switch" hwname = switch dbhw_ent = HardwareEntity.get_unique(session, hwname, hardware_type=hwtype, compel=True) dbinterface = Interface.get_unique(session, hardware_entity=dbhw_ent, name=interface, compel=True) dbnet_env = NetworkEnvironment.get_unique_or_default(session, network_environment) oldinfo = DSDBRunner.snapshot_hw(dbhw_ent) if fqdn: dbdns_rec = ARecord.get_unique(session, fqdn=fqdn, dns_environment=dbnet_env.dns_environment, compel=True) ip = dbdns_rec.ip addr = None if ip: addr = first_of(dbinterface.assignments, lambda x: x.ip == ip) if not addr: raise ArgumentError("{0} does not have IP address {1} assigned to " "it.".format(dbinterface, ip)) elif label is not None: addr = first_of(dbinterface.assignments, lambda x: x.label == label) if not addr: raise ArgumentError("{0} does not have an address with label " "{1}.".format(dbinterface, label)) if not addr: raise ArgumentError("Please specify the address to be removed " "using either --ip, --label, or --fqdn.") dbnetwork = addr.network ip = addr.ip if dbnetwork.network_environment != dbnet_env: raise ArgumentError( "The specified address lives in {0:l}, not in " "{1:l}. Use the --network_environment option " "to select the correct environment.".format(dbnetwork.network_environment, dbnet_env) ) # Forbid removing the primary name if ip == dbhw_ent.primary_ip: raise ArgumentError("The primary IP address of a hardware entity " "cannot be removed.") dbinterface.assignments.remove(addr) # Check if the address was assigned to multiple interfaces, and remove # the DNS entries if this was the last use q = session.query(AddressAssignment) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) other_uses = q.all() if not other_uses and not keep_dns: q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) q = q.join(ARecord.fqdn) q = q.filter_by(dns_environment=dbnet_env.dns_environment) map(delete_dns_record, q.all()) session.flush() dbhost = getattr(dbhw_ent, "host", None) if dbhost: plenary_info = PlenaryHost(dbhost, logger=logger) key = plenary_info.get_write_key() try: lock_queue.acquire(key) try: plenary_info.write(locked=True) except IncompleteError: # FIXME: if this command is used after "add host" but before # "make", then writing out the template will fail due to # required services not being assigned. Ignore this error # for now. plenary_info.restore_stash() dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbhw_ent, oldinfo) if not other_uses and keep_dns: q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) dbdns_rec = q.first() dsdb_runner.add_host_details(dbdns_rec.fqdn, ip) dsdb_runner.commit_or_rollback("Could not add host to DSDB") except: plenary_info.restore_stash() raise finally: lock_queue.release(key) else: dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbhw_ent, oldinfo) dsdb_runner.commit_or_rollback("Could not add host to DSDB") return
def render(self, session, logger, machine, chassis, switch, interface, fqdn, ip, label, keep_dns, network_environment, **kwargs): if machine: hwtype = 'machine' hwname = machine elif chassis: hwtype = 'chassis' hwname = chassis elif switch: hwtype = 'switch' hwname = switch dbhw_ent = HardwareEntity.get_unique(session, hwname, hardware_type=hwtype, compel=True) dbinterface = Interface.get_unique(session, hardware_entity=dbhw_ent, name=interface, compel=True) dbnet_env = NetworkEnvironment.get_unique_or_default( session, network_environment) oldinfo = DSDBRunner.snapshot_hw(dbhw_ent) if fqdn: dbdns_rec = ARecord.get_unique( session, fqdn=fqdn, dns_environment=dbnet_env.dns_environment, compel=True) ip = dbdns_rec.ip addr = None if ip: addr = first_of(dbinterface.assignments, lambda x: x.ip == ip) if not addr: raise ArgumentError( "{0} does not have IP address {1} assigned to " "it.".format(dbinterface, ip)) elif label is not None: addr = first_of(dbinterface.assignments, lambda x: x.label == label) if not addr: raise ArgumentError("{0} does not have an address with label " "{1}.".format(dbinterface, label)) if not addr: raise ArgumentError("Please specify the address to be removed " "using either --ip, --label, or --fqdn.") dbnetwork = addr.network ip = addr.ip if dbnetwork.network_environment != dbnet_env: raise ArgumentError("The specified address lives in {0:l}, not in " "{1:l}. Use the --network_environment option " "to select the correct environment.".format( dbnetwork.network_environment, dbnet_env)) # Forbid removing the primary name if ip == dbhw_ent.primary_ip: raise ArgumentError("The primary IP address of a hardware entity " "cannot be removed.") dbinterface.assignments.remove(addr) # Check if the address was assigned to multiple interfaces, and remove # the DNS entries if this was the last use q = session.query(AddressAssignment) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) other_uses = q.all() if not other_uses and not keep_dns: q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) q = q.join(ARecord.fqdn) q = q.filter_by(dns_environment=dbnet_env.dns_environment) map(delete_dns_record, q.all()) session.flush() dbhost = getattr(dbhw_ent, "host", None) if dbhost: plenary_info = PlenaryHost(dbhost, logger=logger) key = plenary_info.get_write_key() try: lock_queue.acquire(key) try: plenary_info.write(locked=True) except IncompleteError: # FIXME: if this command is used after "add host" but before # "make", then writing out the template will fail due to # required services not being assigned. Ignore this error # for now. plenary_info.restore_stash() dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbhw_ent, oldinfo) if not other_uses and keep_dns: q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) dbdns_rec = q.first() dsdb_runner.add_host_details(dbdns_rec.fqdn, ip) dsdb_runner.commit_or_rollback("Could not add host to DSDB") except: plenary_info.restore_stash() raise finally: lock_queue.release(key) else: dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbhw_ent, oldinfo) dsdb_runner.commit_or_rollback("Could not add host to DSDB") return
def generate_ip(session, logger, dbinterface, ip=None, ipfromip=None, ipfromsystem=None, autoip=None, ipalgorithm=None, compel=False, network_environment=None, audit_results=None, **kwargs): ip_options = [ip, ipfromip, ipfromsystem, autoip] numopts = sum([1 if opt else 0 for opt in ip_options]) if numopts > 1: raise ArgumentError("Only one of --ip, --ipfromip, --ipfromsystem " "and --autoip can be specified.") elif numopts == 0: if compel: raise ArgumentError("Please specify one of the --ip, --ipfromip, " "--ipfromsystem, and --autoip parameters.") return None if ip: return ip dbsystem = None dbnetwork = None if autoip: if not dbinterface: raise ArgumentError("No interface available to automatically " "generate an IP address.") if dbinterface.port_group: # This could either be an interface from a virtual machine # or an interface on an ESX vmhost. dbcluster = None if getattr(dbinterface.hardware_entity, "cluster", None): # VM dbcluster = dbinterface.hardware_entity.cluster elif getattr(dbinterface.hardware_entity, "host", None): dbcluster = dbinterface.hardware_entity.host.cluster if not dbcluster: raise ArgumentError("Can only automatically assign an IP " "address to an interface with a port " "group on virtual machines or ESX hosts.") if not dbcluster.switch: raise ArgumentError( "Cannot automatically assign an IP " "address to an interface with a port group " "since {0} is not associated with a " "switch.".format(dbcluster)) vlan_id = VlanInfo.get_vlan_id(session, dbinterface.port_group) dbnetwork = ObservedVlan.get_network(session, vlan_id=vlan_id, switch=dbcluster.switch, compel=ArgumentError) elif dbinterface.mac: q = session.query(ObservedMac) q = q.filter_by(mac_address=dbinterface.mac) q = q.order_by(desc(ObservedMac.last_seen)) dbom = q.first() if not dbom: raise ArgumentError("No switch found in the discovery table " "for MAC address %s." % dbinterface.mac) if not dbom.switch.primary_ip: raise ArgumentError("{0} does not have a primary IP address " "to use for network " "selection.".format(dbom.switch)) dbnetwork = get_net_id_from_ip(session, dbom.switch.primary_ip) else: raise ArgumentError( "{0} has neither a MAC address nor port group " "information, it is not possible to generate " "an IP address automatically.".format(dbinterface)) if ipfromsystem: # Assumes one system entry, not necessarily correct. dbdns_rec = ARecord.get_unique(session, fqdn=ipfromsystem, compel=True) dbnetwork = dbdns_rec.network if ipfromip: # determine network dbnetwork = get_net_id_from_ip(session, ipfromip, network_environment) if not dbnetwork: raise ArgumentError("Could not determine network to use for %s." % dbsystem.fqdn) # When there are e.g. multiple "add manager --autoip" operations going on in # parallel, we must ensure that they won't try to use the same IP address. # This query places a database lock on the network, which means IP address # generation within a network will be serialized, while operations on # different networks can still run in parallel. The lock will be released by # COMMIT or ROLLBACK. dbnetwork.lock_row() startip = dbnetwork.first_usable_host used_ips = session.query(ARecord.ip) used_ips = used_ips.filter_by(network=dbnetwork) used_ips = used_ips.filter(ARecord.ip >= startip) full_set = set(range(int(startip), int(dbnetwork.broadcast))) used_set = set([int(item.ip) for item in used_ips]) free_set = full_set - used_set if not free_set: raise ArgumentError("No available IP addresses found on " "network %s." % str(dbnetwork.network)) if ipalgorithm is None or ipalgorithm == 'lowest': # Select the lowest available address ip = IPv4Address(min(free_set)) elif ipalgorithm == 'highest': # Select the highest available address ip = IPv4Address(max(free_set)) elif ipalgorithm == 'max': # Return the max. used address + 1 if not used_set: # Avoids ValueError being thrown when used_set is empty ip = IPv4Address(min(free_set)) else: next = max(used_set) if not next + 1 in free_set: raise ArgumentError("Failed to find an IP that is suitable " "for --ipalgorithm=max. Try an other " "algorithm as there are still some free " "addresses.") ip = IPv4Address(next + 1) else: raise ArgumentError("Unknown algorithm %s." % ipalgorithm) if audit_results is not None: if dbinterface: logger.info("Selected IP address {0!s} for {1:l}".format( ip, dbinterface)) else: logger.info("Selected IP address %s" % ip) audit_results.append(('ip', ip)) return ip
def generate_ip(session, logger, dbinterface, ip=None, ipfromip=None, ipfromsystem=None, autoip=None, ipalgorithm=None, compel=False, network_environment=None, audit_results=None, **kwargs): ip_options = [ip, ipfromip, ipfromsystem, autoip] numopts = sum([1 if opt else 0 for opt in ip_options]) if numopts > 1: raise ArgumentError("Only one of --ip, --ipfromip, --ipfromsystem " "and --autoip can be specified.") elif numopts == 0: if compel: raise ArgumentError("Please specify one of the --ip, --ipfromip, " "--ipfromsystem, and --autoip parameters.") return None if ip: return ip dbsystem = None dbnetwork = None if autoip: if not dbinterface: raise ArgumentError("No interface available to automatically " "generate an IP address.") if dbinterface.port_group: # This could either be an interface from a virtual machine # or an interface on an ESX vmhost. dbcluster = None if getattr(dbinterface.hardware_entity, "cluster", None): # VM dbcluster = dbinterface.hardware_entity.cluster elif getattr(dbinterface.hardware_entity, "host", None): dbcluster = dbinterface.hardware_entity.host.cluster if not dbcluster: raise ArgumentError("Can only automatically assign an IP " "address to an interface with a port " "group on virtual machines or ESX hosts.") if not dbcluster.switch: raise ArgumentError("Cannot automatically assign an IP " "address to an interface with a port group " "since {0} is not associated with a " "switch.".format(dbcluster)) vlan_id = VlanInfo.get_vlan_id(session, dbinterface.port_group) dbnetwork = ObservedVlan.get_network(session, vlan_id=vlan_id, switch=dbcluster.switch, compel=ArgumentError) elif dbinterface.mac: q = session.query(ObservedMac) q = q.filter_by(mac_address=dbinterface.mac) q = q.order_by(desc(ObservedMac.last_seen)) dbom = q.first() if not dbom: raise ArgumentError("No switch found in the discovery table " "for MAC address %s." % dbinterface.mac) if not dbom.switch.primary_ip: raise ArgumentError("{0} does not have a primary IP address " "to use for network " "selection.".format(dbom.switch)) dbnetwork = get_net_id_from_ip(session, dbom.switch.primary_ip) else: raise ArgumentError("{0} has neither a MAC address nor port group " "information, it is not possible to generate " "an IP address automatically." .format(dbinterface)) if ipfromsystem: # Assumes one system entry, not necessarily correct. dbdns_rec = ARecord.get_unique(session, fqdn=ipfromsystem, compel=True) dbnetwork = dbdns_rec.network if ipfromip: # determine network dbnetwork = get_net_id_from_ip(session, ipfromip, network_environment) if not dbnetwork: raise ArgumentError("Could not determine network to use for %s." % dbsystem.fqdn) # When there are e.g. multiple "add manager --autoip" operations going on in # parallel, we must ensure that they won't try to use the same IP address. # This query places a database lock on the network, which means IP address # generation within a network will be serialized, while operations on # different networks can still run in parallel. The lock will be released by # COMMIT or ROLLBACK. dbnetwork.lock_row() startip = dbnetwork.first_usable_host used_ips = session.query(ARecord.ip) used_ips = used_ips.filter_by(network=dbnetwork) used_ips = used_ips.filter(ARecord.ip >= startip) full_set = set(range(int(startip), int(dbnetwork.broadcast))) used_set = set([int(item.ip) for item in used_ips]) free_set = full_set - used_set if not free_set: raise ArgumentError("No available IP addresses found on " "network %s." % str(dbnetwork.network)) if ipalgorithm is None or ipalgorithm == 'lowest': # Select the lowest available address ip = IPv4Address(min(free_set)) elif ipalgorithm == 'highest': # Select the highest available address ip = IPv4Address(max(free_set)) elif ipalgorithm == 'max': # Return the max. used address + 1 if not used_set: # Avoids ValueError being thrown when used_set is empty ip = IPv4Address(min(free_set)) else: next = max(used_set) if not next + 1 in free_set: raise ArgumentError("Failed to find an IP that is suitable " "for --ipalgorithm=max. Try an other " "algorithm as there are still some free " "addresses.") ip = IPv4Address(next + 1) else: raise ArgumentError("Unknown algorithm %s." % ipalgorithm) if audit_results is not None: if dbinterface: logger.info("Selected IP address {0!s} for {1:l}" .format(ip, dbinterface)) else: logger.info("Selected IP address %s" % ip) audit_results.append(('ip', ip)) return ip
def grab_address(session, fqdn, ip, network_environment=None, dns_environment=None, comments=None, allow_restricted_domain=False, allow_multi=False, allow_reserved=False, relaxed=False, preclude=False): """ Take ownership of an address. This is a bit complicated because due to DNS propagation delays, we want to allow users to pre-define a DNS address and then assign the address to a host later. Parameters: session: SQLA session handle fqdn: the name to allocate/take over ip: the IP address to allocate/take over network_environment: where the IP address lives dns_enviromnent: where the FQDN lives comments: any comments to attach to the DNS record if it is created as new allow_restricted_domain: if True, adding entries to restricted DNS domains is allowed, otherwise it is denied. Default is False. allow_multi: if True, allow the same FQDN to be added multiple times with different IP addresses. Deault is False. allow_reserved: if True, allow creating a ReservedName instead of an ARecord if no IP address was specified. Default is False. preclude: if True, forbid taking over an existing DNS record, even if it is not referenced by any AddressAssignment records. Default is False. """ if not isinstance(network_environment, NetworkEnvironment): network_environment = NetworkEnvironment.get_unique_or_default( session, network_environment) if not dns_environment: dns_environment = network_environment.dns_environment elif not isinstance(dns_environment, DnsEnvironment): dns_environment = DnsEnvironment.get_unique(session, dns_environment, compel=True) # Non-default DNS environments may contain anything, but we want to keep # the internal environment clean if dns_environment.is_default and not network_environment.is_default: raise ArgumentError("Entering external IP addresses to the " "internal DNS environment is not allowed.") short, dbdns_domain = parse_fqdn(session, fqdn) # Lock the domain to prevent adding/deleting records while we're checking # FQDN etc. availability dbdns_domain.lock_row() if dbdns_domain.restricted and not allow_restricted_domain: raise ArgumentError("{0} is restricted, adding extra addresses " "is not allowed.".format(dbdns_domain)) dbfqdn = Fqdn.get_or_create(session, dns_environment=dns_environment, name=short, dns_domain=dbdns_domain, query_options=[joinedload('dns_records')]) existing_record = None newly_created = False if ip: dbnetwork = get_net_id_from_ip(session, ip, network_environment) check_ip_restrictions(dbnetwork, ip, relaxed=relaxed) dbnetwork.lock_row() # No filtering on DNS environment. If an address is dynamic in one # environment, it should not be considered static in a different # environment. q = session.query(DynamicStub) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) dbdns_rec = q.first() _forbid_dyndns(dbdns_rec) # Verify that no other record uses the same IP address, this time taking # the DNS environemt into consideration. # While the DNS would allow different A records to point to the same IP # address, the current user expectation is that creating a DNS entry # also counts as a reservation, so we can not allow this use case. If we # want to implement such a feature later, the best way would be to # subclass Alias and let that subclass emit an A record instead of a # CNAME when the dump_dns command is called. q = session.query(ARecord) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) q = q.join(ARecord.fqdn) q = q.filter_by(dns_environment=dns_environment) dbrecords = q.all() if dbrecords and len(dbrecords) > 1: # pragma: no cover # We're just trying to make sure this never happens raise AquilonError( "IP address %s is referenced by multiple " "DNS records: %s" % (ip, ", ".join([format(rec, "a") for rec in dbrecords]))) if dbrecords and dbrecords[0].fqdn != dbfqdn: raise ArgumentError( "IP address {0} is already in use by {1:l}.".format( ip, dbrecords[0])) # Check if the name is used already for dbdns_rec in dbfqdn.dns_records: if isinstance(dbdns_rec, ARecord): _forbid_dyndns(dbdns_rec) _check_netenv_compat(dbdns_rec, network_environment) if dbdns_rec.ip == ip and dbdns_rec.network == dbnetwork: existing_record = dbdns_rec elif not allow_multi: raise ArgumentError( "{0} points to a different IP address.".format( dbdns_rec)) elif isinstance(dbdns_rec, ReservedName): existing_record = convert_reserved_to_arecord( session, dbdns_rec, dbnetwork, ip) newly_created = True else: # Exclude aliases etc. raise ArgumentError( "{0} cannot be used for address assignment.".format( dbdns_rec)) if not existing_record: existing_record = ARecord(fqdn=dbfqdn, ip=ip, network=dbnetwork, comments=comments) session.add(existing_record) newly_created = True else: if not dbfqdn.dns_records: # There's no IP, and the name did not exist before. Create a # reservation, but only if the caller allowed that use case. if not allow_reserved: raise ArgumentError("DNS Record %s does not exist." % dbfqdn) existing_record = ReservedName(fqdn=dbfqdn, comments=comments) newly_created = True else: # There's no IP, but the name is already in use. We need a single IP # address. if len(dbfqdn.dns_records) > 1: raise ArgumentError( "{0} does not resolve to a single IP address.".format( dbfqdn)) existing_record = dbfqdn.dns_records[0] _forbid_dyndns(existing_record) if not isinstance(existing_record, ARecord): # Exclude aliases etc. raise ArgumentError( "{0} cannot be used for address assignment.".format( existing_record)) # Verify that the existing record is in the network environment the # caller expects _check_netenv_compat(existing_record, network_environment) ip = existing_record.ip dbnetwork = existing_record.network dbnetwork.lock_row() if existing_record.hardware_entity: raise ArgumentError( "{0} is already used as the primary name of {1:cl} " "{1.label}.".format(existing_record, existing_record.hardware_entity)) if preclude and not newly_created: raise ArgumentError("{0} already exists.".format(existing_record)) if ip: q = session.query(AddressAssignment) q = q.filter_by(network=dbnetwork) q = q.filter_by(ip=ip) addr = q.first() if addr: raise ArgumentError("IP address {0} is already in use by " "{1:l}.".format(ip, addr.interface)) return (existing_record, newly_created)
class CommandAddInterfaceMachine(BrokerCommand): required_parameters = ["interface", "machine"] def render(self, session, logger, interface, machine, mac, automac, model, vendor, pg, autopg, type, comments, **arguments): dbmachine = Machine.get_unique(session, machine, compel=True) oldinfo = DSDBRunner.snapshot_hw(dbmachine) audit_results = [] q = session.query(Interface) q = q.filter_by(name=interface, hardware_entity=dbmachine) if q.first(): raise ArgumentError( "Machine %s already has an interface named %s." % (machine, interface)) if not type: type = 'public' management_types = ['bmc', 'ilo', 'ipmi'] for mtype in management_types: if interface.startswith(mtype): type = 'management' break if interface.startswith("bond"): type = 'bonding' elif interface.startswith("br"): type = 'bridge' # Test it last, VLANs can be added on top of almost anything if '.' in interface: type = 'vlan' if type == "oa" or type == "loopback": raise ArgumentError("Interface type '%s' is not valid for " "machines." % type) bootable = None if type == 'public': if interface == 'eth0': bootable = True else: bootable = False dbmanager = None pending_removals = PlenaryCollection() dsdb_runner = DSDBRunner(logger=logger) if mac: prev = session.query(Interface).filter_by(mac=mac).first() if prev and prev.hardware_entity == dbmachine: raise ArgumentError("{0} already has an interface with MAC " "address {1}.".format(dbmachine, mac)) # Is the conflicting interface something that can be # removed? It is if: # - we are currently attempting to add a management interface # - the old interface belongs to a machine # - the old interface is associated with a host # - that host was blindly created, and thus can be removed safely if prev and type == 'management' and \ prev.hardware_entity.hardware_type == 'machine' and \ prev.hardware_entity.host and \ prev.hardware_entity.host.status.name == 'blind': # FIXME: Is this just always allowed? Maybe restrict # to only aqd-admin and the host itself? dummy_machine = prev.hardware_entity dummy_ip = dummy_machine.primary_ip old_fqdn = str(dummy_machine.primary_name) old_iface = prev.name old_mac = prev.mac old_network = get_net_id_from_ip(session, dummy_ip) self.remove_prev(session, logger, prev, pending_removals) session.flush() dsdb_runner.delete_host_details(old_fqdn, dummy_ip, old_iface, old_mac) self.consolidate_names(session, logger, dbmachine, dummy_machine.label, pending_removals) # It seems like a shame to throw away the IP address that # had been allocated for the blind host. Try to use it # as it should be used... dbmanager = self.add_manager(session, logger, dbmachine, dummy_ip, old_network) elif prev: msg = describe_interface(session, prev) raise ArgumentError("MAC address %s is already in use: %s." % (mac, msg)) elif automac: mac = self.generate_mac(session, dbmachine) audit_results.append(('mac', mac)) else: #Ignore now that Mac Address can be null pass if pg is not None: port_group = verify_port_group(dbmachine, pg) elif autopg: port_group = choose_port_group(session, logger, dbmachine) audit_results.append(('pg', port_group)) else: port_group = None dbinterface = get_or_create_interface(session, dbmachine, name=interface, vendor=vendor, model=model, interface_type=type, mac=mac, bootable=bootable, port_group=port_group, comments=comments, preclude=True) # So far, we're *only* creating a manager if we happen to be # removing a blind entry and we can steal its IP address. if dbmanager: assign_address(dbinterface, dbmanager.ip, dbmanager.network) session.add(dbinterface) session.flush() plenaries = PlenaryCollection(logger=logger) plenaries.append(Plenary.get_plenary(dbmachine)) if pending_removals and dbmachine.host: # Not an exact test, but the file won't be re-written # if the contents are the same so calling too often is # not a major expense. plenaries.append(Plenary.get_plenary(dbmachine.host)) # Even though there may be removals going on the write key # should be sufficient here. key = plenaries.get_write_key() try: lock_queue.acquire(key) pending_removals.stash() plenaries.write(locked=True) pending_removals.remove(locked=True) dsdb_runner.update_host(dbmachine, oldinfo) dsdb_runner.commit_or_rollback("Could not update host in DSDB") except: plenaries.restore_stash() pending_removals.restore_stash() raise finally: lock_queue.release(key) if dbmachine.host: # FIXME: reconfigure host pass for name, value in audit_results: self.audit_result(session, name, value, **arguments) return def remove_prev(self, session, logger, prev, pending_removals): """Remove the interface 'prev' and its host and machine.""" # This should probably be re-factored to call code used elsewhere. # The below seems too simple to warrant that, though... logger.info( "Removing blind host '%s', machine '%s', " "and interface '%s'" % (prev.hardware_entity.fqdn, prev.hardware_entity.label, prev.name)) host_plenary_info = Plenary.get_plenary(prev.hardware_entity.host, logger=logger) # FIXME: Should really do everything that del_host.py does, not # just remove the host plenary but adjust all the service # plenarys and dependency files. pending_removals.append(host_plenary_info) dbmachine = prev.hardware_entity machine_plenary_info = Plenary.get_plenary(dbmachine, logger=logger) pending_removals.append(machine_plenary_info) # This will cascade to prev & the host if dbmachine.primary_name: dbdns_rec = dbmachine.primary_name dbmachine.primary_name = None delete_dns_record(dbdns_rec) session.delete(dbmachine) session.flush() def consolidate_names(self, session, logger, dbmachine, dummy_machine_name, pending_removals): short = dbmachine.label[:-1] if short != dummy_machine_name[:-1]: logger.client_info("Not altering name of machine %s, name of " "machine being removed %s is too different." % (dbmachine.label, dummy_machine_name)) return if not dbmachine.label[-1].isalpha(): logger.client_info("Not altering name of machine %s, name does " "not end with a letter." % dbmachine.label) return if session.query(Machine).filter_by(label=short).first(): logger.client_info("Not altering name of machine %s, target " "name %s is already in use." % (dbmachine.label, short)) return logger.client_info("Renaming machine %s to %s." % (dbmachine.label, short)) pending_removals.append(Plenary.get_plenary(dbmachine)) dbmachine.label = short session.add(dbmachine) session.flush() def add_manager(self, session, logger, dbmachine, old_ip, old_network): if not old_ip: logger.client_info("No IP address available for system being " "removed, not auto-creating manager for %s." % dbmachine.label) return if not dbmachine.host: logger.client_info("Machine %s is not linked to a host, not " "auto-creating manager with IP address " "%s." % (dbmachine.label, old_ip)) return manager = "%sr.%s" % (dbmachine.primary_name.fqdn.name, dbmachine.primary_name.fqdn.dns_domain.name) try: dbfqdn = Fqdn.get_or_create(session, fqdn=manager, preclude=True) except ArgumentError, e: logger.client_info("Could not create manager with name %s and " "IP address %s for machine %s: %s" % (manager, old_ip, dbmachine.label, e)) return dbmanager = ARecord(fqdn=dbfqdn, ip=old_ip, network=old_network) session.add(dbmanager) return dbmanager