def render(self, session, logger, chassis, label, rack, model, vendor, ip, interface, mac, serial, comments, **arguments): dbdns_rec, newly_created = grab_address(session, chassis, ip, allow_restricted_domain=True, allow_reserved=True, preclude=True) if not label: label = dbdns_rec.fqdn.name try: Chassis.check_label(label) except ArgumentError: raise ArgumentError("Could not deduce a valid hardware label " "from the chassis name. Please specify " "--label.") dblocation = get_location(session, rack=rack) dbmodel = Model.get_unique(session, name=model, vendor=vendor, machine_type='chassis', compel=True) # FIXME: Precreate chassis slots? dbchassis = Chassis(label=label, location=dblocation, model=dbmodel, serial_no=serial, comments=comments) session.add(dbchassis) dbchassis.primary_name = dbdns_rec # FIXME: get default name from the model if not interface: interface = "oa" ifcomments = "Created automatically by add_chassis" else: ifcomments = None dbinterface = get_or_create_interface(session, dbchassis, name=interface, mac=mac, interface_type="oa", comments=ifcomments) if ip: dbnetwork = get_net_id_from_ip(session, ip) check_ip_restrictions(dbnetwork, ip) assign_address(dbinterface, ip, dbnetwork) session.flush() if ip: dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbchassis, None) dsdb_runner.commit_or_rollback("Could not add chassis to DSDB") return
def update_primary_ip(session, dbhw_ent, ip): if not dbhw_ent.primary_name: raise ArgumentError( "{0} does not have a primary name.".format(dbhw_ent)) dbnetwork = get_net_id_from_ip(session, ip) check_ip_restrictions(dbnetwork, ip) # The primary address must be unique 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)) # Convert ReservedName to ARecord if needed if isinstance(dbhw_ent.primary_name, ReservedName): convert_reserved_to_arecord(session, dbhw_ent.primary_name, dbnetwork, ip) # When converting a ReservedName to an ARecord, we have to bind the # primary address to an interface. Try to pick one. dbinterface = first_of(dbhw_ent.interfaces, lambda x: x.bootable) if not dbinterface: dbinterface = first_of(dbhw_ent.interfaces, lambda x: x.interface_type != "management") if not dbinterface: # pragma: no cover raise AquilonError("Cannot update the primary IP address of {0:l} " "because it does not have any interfaces " "defined.".format(dbhw_ent)) assign_address(dbinterface, ip, dbnetwork) else: dns_rec = dbhw_ent.primary_name q = session.query(AddressAssignment) q = q.filter_by(network=dns_rec.network) q = q.filter_by(ip=dns_rec.ip) q = q.join(Interface) q = q.filter_by(hardware_entity=dbhw_ent) # In case of Zebra, the address may be assigned to multiple interfaces addrs = q.all() dns_rec.ip = ip dns_rec.network = dbnetwork for addr in addrs: addr.ip = ip addr.network = dbnetwork
def update_primary_ip(session, logger, dbhw_ent, ip): if not dbhw_ent.primary_name: raise ArgumentError("{0} does not have a primary name." .format(dbhw_ent)) dbnetwork = get_net_id_from_ip(session, ip) check_ip_restrictions(dbnetwork, ip) # The primary address must be unique 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)) # Convert ReservedName to ARecord if needed if isinstance(dbhw_ent.primary_name, ReservedName): convert_reserved_to_arecord(session, dbhw_ent.primary_name, dbnetwork, ip) # When converting a ReservedName to an ARecord, we have to bind the # primary address to an interface. Try to pick one. dbinterface = first_of(dbhw_ent.interfaces, lambda x: x.bootable) if not dbinterface: dbinterface = first_of(dbhw_ent.interfaces, lambda x: x.interface_type != "management") if not dbinterface: # pragma: no cover raise AquilonError("Cannot update the primary IP address of {0:l} " "because it does not have any interfaces " "defined.".format(dbhw_ent)) assign_address(dbinterface, ip, dbnetwork, logger=logger) else: dns_rec = dbhw_ent.primary_name q = session.query(AddressAssignment) q = q.filter_by(network=dns_rec.network) q = q.filter_by(ip=dns_rec.ip) q = q.join(Interface) q = q.filter_by(hardware_entity=dbhw_ent) # In case of Zebra, the address may be assigned to multiple interfaces addrs = q.all() dns_rec.ip = ip dns_rec.network = dbnetwork for addr in addrs: addr.ip = ip addr.network = dbnetwork
def render(self, session, logger, chassis, label, rack, model, vendor, ip, interface, mac, serial, comments, **arguments): dbdns_rec, newly_created = grab_address(session, chassis, ip, allow_restricted_domain=True, allow_reserved=True, preclude=True) if not label: label = dbdns_rec.fqdn.name try: Chassis.check_label(label) except ArgumentError: raise ArgumentError("Could not deduce a valid hardware label " "from the chassis name. Please specify " "--label.") dblocation = get_location(session, rack=rack) dbmodel = Model.get_unique(session, name=model, vendor=vendor, machine_type='chassis', compel=True) # FIXME: Precreate chassis slots? dbchassis = Chassis(label=label, location=dblocation, model=dbmodel, serial_no=serial, comments=comments) session.add(dbchassis) dbchassis.primary_name = dbdns_rec # FIXME: get default name from the model if not interface: interface = "oa" ifcomments = "Created automatically by add_chassis" else: ifcomments = None dbinterface = get_or_create_interface(session, dbchassis, name=interface, mac=mac, interface_type="oa", comments=ifcomments) if ip: dbnetwork = get_net_id_from_ip(session, ip) check_ip_restrictions(dbnetwork, ip) assign_address(dbinterface, ip, dbnetwork) session.flush() if ip: dsdb_runner = DSDBRunner(logger=logger) dsdb_runner.update_host(dbchassis, None) dsdb_runner.commit_or_rollback("Could not add chassis to DSDB") return
def render(self, session, logger, startip, endip, dns_domain, prefix, **arguments): if not prefix: prefix = 'dynamic' dbnet_env = NetworkEnvironment.get_unique_or_default(session) dbdns_env = DnsEnvironment.get_unique_or_default(session) startnet = get_net_id_from_ip(session, startip, dbnet_env) endnet = get_net_id_from_ip(session, endip, dbnet_env) if startnet != endnet: raise ArgumentError("IP addresses %s (%s) and %s (%s) must be on " "the same subnet." % (startip, startnet.ip, endip, endnet.ip)) dbdns_domain = DnsDomain.get_unique(session, dns_domain, compel=True) dbdns_domain.lock_row() startnet.lock_row() q = session.query(AddressAssignment.ip) q = q.filter_by(network=startnet) q = q.filter(AddressAssignment.ip >= startip) q = q.filter(AddressAssignment.ip <= endip) q = q.order_by(AddressAssignment.ip) conflicts = q.all() if conflicts: raise ArgumentError("Cannot allocate the address range because the " "following IP addresses are already in use:\n" + ", ".join([str(c.ip) for c in conflicts])) # 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(ARecord) q = q.filter_by(network=startnet) q = q.filter(ARecord.ip >= startip) q = q.filter(ARecord.ip <= endip) q = q.order_by(ARecord.ip) conflicts = q.all() if conflicts: raise ArgumentError("Cannot allocate the address range because the " "following DNS records already exist:\n" + "\n".join([format(c, "a") for c in conflicts])) dsdb_runner = DSDBRunner(logger=logger) with session.no_autoflush: for ipint in range(int(startip), int(endip) + 1): ip = IPv4Address(ipint) check_ip_restrictions(startnet, ip) name = "%s-%s" % (prefix, str(ip).replace('.', '-')) dbfqdn = Fqdn.get_or_create(session, name=name, dns_domain=dbdns_domain, dns_environment=dbdns_env, preclude=True) dbdynamic_stub = DynamicStub(fqdn=dbfqdn, ip=ip, network=startnet) session.add(dbdynamic_stub) dsdb_runner.add_host_details(dbfqdn, ip) session.flush() # This may take some time if the range is big, so be verbose dsdb_runner.commit_or_rollback("Could not add addresses to DSDB", verbose=True) return
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)
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)
def render(self, session, logger, startip, endip, dns_domain, prefix, **arguments): if not prefix: prefix = 'dynamic' dbnet_env = NetworkEnvironment.get_unique_or_default(session) dbdns_env = DnsEnvironment.get_unique_or_default(session) startnet = get_net_id_from_ip(session, startip, dbnet_env) endnet = get_net_id_from_ip(session, endip, dbnet_env) if startnet != endnet: raise ArgumentError("IP addresses %s (%s) and %s (%s) must be on " "the same subnet." % (startip, startnet.ip, endip, endnet.ip)) dbdns_domain = DnsDomain.get_unique(session, dns_domain, compel=True) dbdns_domain.lock_row() startnet.lock_row() q = session.query(AddressAssignment.ip) q = q.filter_by(network=startnet) q = q.filter(AddressAssignment.ip >= startip) q = q.filter(AddressAssignment.ip <= endip) q = q.order_by(AddressAssignment.ip) conflicts = q.all() if conflicts: raise ArgumentError( "Cannot allocate the address range because the " "following IP addresses are already in use:\n" + ", ".join([str(c.ip) for c in conflicts])) # 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(ARecord) q = q.filter_by(network=startnet) q = q.filter(ARecord.ip >= startip) q = q.filter(ARecord.ip <= endip) q = q.order_by(ARecord.ip) conflicts = q.all() if conflicts: raise ArgumentError( "Cannot allocate the address range because the " "following DNS records already exist:\n" + "\n".join([format(c, "a") for c in conflicts])) dsdb_runner = DSDBRunner(logger=logger) with session.no_autoflush: for ipint in range(int(startip), int(endip) + 1): ip = IPv4Address(ipint) check_ip_restrictions(startnet, ip) name = "%s-%s" % (prefix, str(ip).replace('.', '-')) dbfqdn = Fqdn.get_or_create(session, name=name, dns_domain=dbdns_domain, dns_environment=dbdns_env, preclude=True) dbdynamic_stub = DynamicStub(fqdn=dbfqdn, ip=ip, network=startnet) session.add(dbdynamic_stub) dsdb_runner.add_host_details(dbfqdn, ip) session.flush() # This may take some time if the range is big, so be verbose dsdb_runner.commit_or_rollback("Could not add addresses to DSDB", verbose=True) return