diff --git a/config/facsimile/peeringdb.yaml b/config/facsimile/peeringdb.yaml index 52815a24..395023db 100644 --- a/config/facsimile/peeringdb.yaml +++ b/config/facsimile/peeringdb.yaml @@ -160,7 +160,6 @@ install: - $SRC_DIR$/peeringdb_server/management/commands/pdb_process_admin_tool_command.py - $SRC_DIR$/peeringdb_server/management/commands/pdb_load_data.py - $SRC_DIR$/peeringdb_server/management/commands/pdb_fix_status_history.py - - $SRC_DIR$/peeringdb_server/management/commands/pdb_migrate_ixlans.py - $SRC_DIR$/peeringdb_server/migrations/__init__.py - $SRC_DIR$/peeringdb_server/migrations/0001_initial.py - $SRC_DIR$/peeringdb_server/migrations/0002_partnernship_model.py diff --git a/peeringdb_server/admin.py b/peeringdb_server/admin.py index 42d90436..d9d6c4f9 100644 --- a/peeringdb_server/admin.py +++ b/peeringdb_server/admin.py @@ -10,7 +10,7 @@ import django.urls from django.conf.urls import url from django.shortcuts import redirect, Http404 from django.contrib.contenttypes.models import ContentType -from django.contrib import admin, messages +from django.contrib import admin from django.contrib.auth import forms from django.contrib.admin import helpers from django.contrib.admin.actions import delete_selected @@ -296,15 +296,6 @@ def soft_delete(modeladmin, request, queryset): if request.user: reversion.set_user(request.user) - if queryset.model.handleref.tag == "ixlan": - messages.error( - request, - _( - "Ixlans can no longer be directly deleted as they are now synced to the parent exchange" - ), - ) - return - for row in queryset: row.delete() @@ -415,13 +406,7 @@ class IXLanInline(SanitizedAdmin, admin.StackedInline): extra = 0 form = StatusForm exclude = ["arp_sponge"] - readonly_fields = ["id", "ixf_import_attempt_info", "prefixes"] - - def has_add_permission(self, request): - return False - - def has_delete_permission(self, request, obj): - return False + readonly_fields = ["ixf_import_attempt_info", "prefixes"] def ixf_import_attempt_info(self, obj): if obj.ixf_import_attempt: @@ -568,7 +553,6 @@ class IXLanAdminForm(StatusForm): class IXLanAdmin(SoftDeleteAdmin): - actions = [] list_display = ("ix", "name", "descr", "status") search_fields = ("name", "ix__name") list_filter = (StatusFilter,) diff --git a/peeringdb_server/autocomplete_views.py b/peeringdb_server/autocomplete_views.py index 0d8f356d..9a64bfe2 100644 --- a/peeringdb_server/autocomplete_views.py +++ b/peeringdb_server/autocomplete_views.py @@ -131,12 +131,13 @@ class IXLanAutocomplete(AutocompleteHTMLResponse): def get_result_label(self, item): return ( - '
%s
%s
%s
' + '
%s
%s
%s
%s
' % ( item.pk, html.escape(item.ix.name), html.escape(item.ix.country.code), html.escape(item.ix.name_long), + html.escape(item.name), ) ) diff --git a/peeringdb_server/client_adaptor/backend.py b/peeringdb_server/client_adaptor/backend.py index 2b7575d0..3a68dfb5 100644 --- a/peeringdb_server/client_adaptor/backend.py +++ b/peeringdb_server/client_adaptor/backend.py @@ -119,10 +119,7 @@ class Backend(BaseBackend): obj.clean() def save(self, obj): - if obj.HandleRef.tag == "ix": - obj.save(create_ixlan=False) - else: - obj.save() + obj.save() def detect_uniqueness_error(self, exc): """ diff --git a/peeringdb_server/management/commands/pdb_api_test.py b/peeringdb_server/management/commands/pdb_api_test.py index 5039c4d5..6a5de782 100644 --- a/peeringdb_server/management/commands/pdb_api_test.py +++ b/peeringdb_server/management/commands/pdb_api_test.py @@ -346,7 +346,6 @@ class TestJSON(unittest.TestCase): def make_data_ixlan(self, **kwargs): data = { "ix_id": 1, - "id": 1, "name": self.make_name("Test"), "descr": NOTE, "mtu": 12345, @@ -354,8 +353,6 @@ class TestJSON(unittest.TestCase): "rs_asn": 12345, "arp_sponge": None, } - if "ix_id" in kwargs: - data["id"] = kwargs.get("ix_id") data.update(**kwargs) return data @@ -654,8 +651,7 @@ class TestJSON(unittest.TestCase): kwargs_s = {"%s_%s" % (rel, qfld): getattr(SHARED["%s_r_ok" % rel], fld)} kwargs_m = {"%s_%s__in" % (rel, qfld): ",".join([str(id) for id in ids])} - attr = getattr(REFTAG_MAP[target], rel, None) - if attr and not isinstance(attr, property): + if hasattr(REFTAG_MAP[target], "%s" % rel): valid_s = [ r.id @@ -1002,11 +998,6 @@ class TestJSON(unittest.TestCase): SHARED["ix_id"] = r_data.get("id") - # make sure ixlan was created and has matching id - ix = InternetExchange.objects.get(id=SHARED["ix_id"]) - assert ix.ixlan - assert ix.ixlan.id == ix.id - self.assert_update( self.db_org_admin, "ix", @@ -1339,23 +1330,23 @@ class TestJSON(unittest.TestCase): def test_org_admin_002_POST_PUT_DELETE_ixlan(self): data = self.make_data_ixlan(ix_id=SHARED["ix_rw_ok"].id) - with self.assertRaises(Exception) as exc: - r_data = self.assert_create( - self.db_org_admin, - "ixlan", - data, - test_failures={ - "invalid": {"ix_id": ""}, - "perms": {"ix_id": SHARED["ix_r_ok"].id}, - "status": {"ix_id": SHARED["ix_rw_pending"].id}, - }, - ) - self.assertIn('Method "POST" not allowed', str(exc.exception)) + r_data = self.assert_create( + self.db_org_admin, + "ixlan", + data, + test_failures={ + "invalid": {"ix_id": ""}, + "perms": {"ix_id": SHARED["ix_r_ok"].id}, + "status": {"ix_id": SHARED["ix_rw_pending"].id}, + }, + ) + + SHARED["ixlan_id"] = r_data["id"] self.assert_update( self.db_org_admin, "ixlan", - SHARED["ixlan_rw_ok"].id, + SHARED["ixlan_id"], {"name": self.make_name("Test")}, test_failures={ "invalid": {"mtu": "NEEDS TO BE INT"}, @@ -1363,14 +1354,12 @@ class TestJSON(unittest.TestCase): }, ) - with self.assertRaises(Exception) as exc: - self.assert_delete( - self.db_org_admin, - "ixlan", - test_success=SHARED["ixlan_rw_ok"].id, - test_failure=SHARED["ixlan_r_ok"].id, - ) - self.assertIn('Method "DELETE" not allowed', str(exc.exception)) + self.assert_delete( + self.db_org_admin, + "ixlan", + test_success=SHARED["ixlan_id"], + test_failure=SHARED["ixlan_r_ok"].id, + ) ########################################################################## @@ -2093,8 +2082,11 @@ class TestJSON(unittest.TestCase): for i in range(0, 2) ] - # collect ixlans - ixlans = [ix.ixlan for ix in exchanges] + # create ixlan at each exchange + ixlans = [ + IXLan.objects.create(status="ok", **self.make_data_ixlan(ix_id=ix.id)) + for ix in exchanges + ] # all three networks peer at first exchange for net in networks: @@ -2599,15 +2591,13 @@ class TestJSON(unittest.TestCase): def test_readonly_users_002_POST_ixlan(self): for db in self.readonly_dbs(): - with self.assertRaises(Exception) as exc: - self.assert_create( - db, - "ixlan", - self.make_data_ixlan(), - test_failures={"perms": {}}, - test_success=False, - ) - self.assertIn('Method "POST" not allowed', str(exc.exception)) + self.assert_create( + db, + "ixlan", + self.make_data_ixlan(), + test_failures={"perms": {}}, + test_success=False, + ) ########################################################################## @@ -2626,14 +2616,9 @@ class TestJSON(unittest.TestCase): def test_readonly_users_004_DELETE_ixlan(self): for db in self.readonly_dbs(): - with self.assertRaises(Exception) as exc: - self.assert_delete( - db, - "ixlan", - test_success=False, - test_failure=SHARED["ixlan_r_ok"].id, - ) - self.assertIn('Method "DELETE" not allowed', str(exc.exception)) + self.assert_delete( + db, "ixlan", test_success=False, test_failure=SHARED["ixlan_r_ok"].id + ) ########################################################################## @@ -2727,6 +2712,16 @@ class TestJSON(unittest.TestCase): test_failures={"perms": {"net_id": SHARED["net_rw2_ok"].id}}, ) + # user with create perms should not be able to create an ixlan under + # net_rw_ix + self.assert_create( + self.db_crud_create, + "ixlan", + self.make_data_ixlan(ix_id=SHARED["ix_rw3_ok"].id), + test_failures={"perms": {}}, + test_success=False, + ) + # other crud test users should not be able to create a new poc under # net_rw3_ok for p in ["delete", "update"]: @@ -3139,9 +3134,7 @@ class Command(BaseCommand): for k in unset: if k in data: del data[k] - obj = model(**data) - obj.save() - + obj = model.objects.create(**data) cls.log( "%s with status '%s' for %s testing created! (%s)" % (tag.upper(), status, prefix.upper(), obj.updated) @@ -3306,12 +3299,12 @@ class Command(BaseCommand): for status in ["ok", "pending"]: for prefix in ["r", "rw"]: - SHARED["ixlan_{}_{}".format(prefix, status)] = SHARED[ - "ix_{}_{}".format(prefix, status) - ].ixlan - - for status in ["ok", "pending"]: - for prefix in ["r", "rw"]: + cls.create_entity( + IXLan, + status=status, + prefix=prefix, + ix_id=SHARED["ix_%s_%s" % (prefix, status)].id, + ) cls.create_entity( IXLanPrefix, status=status, diff --git a/peeringdb_server/management/commands/pdb_generate_test_data.py b/peeringdb_server/management/commands/pdb_generate_test_data.py index f9d4a267..9fb56308 100644 --- a/peeringdb_server/management/commands/pdb_generate_test_data.py +++ b/peeringdb_server/management/commands/pdb_generate_test_data.py @@ -58,6 +58,7 @@ class Command(BaseCommand): "net", "ix", "fac", + "ixlan", "ixpfx", "ixfac", "netixlan", @@ -103,8 +104,6 @@ class Command(BaseCommand): params.update(protocol="IPv6") entity = self.mock.create(reftag, **params) self.entities[reftag].append(entity) - elif reftag == "ix": - self.entities["ixlan"].append(entity.ixlan) self.entities["net"].append(self.mock.create("net")) self.entities["ix"].append(self.mock.create("ix")) diff --git a/peeringdb_server/migrations/0022_ixlan_remove_auto_increment.py b/peeringdb_server/migrations/0022_ixlan_remove_auto_increment.py index dacfca19..adbb95ff 100644 --- a/peeringdb_server/migrations/0022_ixlan_remove_auto_increment.py +++ b/peeringdb_server/migrations/0022_ixlan_remove_auto_increment.py @@ -12,9 +12,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name="ixlan", - name="id", - field=models.IntegerField(primary_key=True, serialize=False), - ), + # this change was reverted, but we will keep this empty migration + # so it does not break the migration chain ] diff --git a/peeringdb_server/mock.py b/peeringdb_server/mock.py index 2cab404b..f496b80f 100644 --- a/peeringdb_server/mock.py +++ b/peeringdb_server/mock.py @@ -96,10 +96,6 @@ class Mock(object): # these we don't care about if field.name in ["id", "logo", "version", "created", "updated"]: continue - # if reftag == "ixlan" and field.name != "id": - # continue - # elif reftag != "ixlan": - # continue # this we dont care about either if field.name.find("geocode") == 0: @@ -144,15 +140,7 @@ class Mock(object): # with the same name as the field name else: data[field.name] = getattr(self, field.name)(data, reftag=reftag) - obj = model(**data) - obj.clean() - obj.save() - return obj - - def id(self, data, reftag=None): - if reftag == "ixlan": - return data["ix"].id - return None + return model.objects.create(**data) def status(self, data, reftag=None): return "ok" diff --git a/peeringdb_server/models.py b/peeringdb_server/models.py index 7b6bf7e3..1634c3c2 100644 --- a/peeringdb_server/models.py +++ b/peeringdb_server/models.py @@ -1335,17 +1335,6 @@ class InternetExchange(pdb_models.InternetExchangeBase): ix_id, ) - @property - def ixlan(self): - """ - Returns the ixlan for this exchange - - As per #21 each exchange will get one ixlan with a matching - id, but the schema is to remain unchanged until a major - version bump. - """ - return self.ixlan_set.first() - @property def networks(self): """ @@ -1433,29 +1422,6 @@ class InternetExchange(pdb_models.InternetExchangeBase): ixpfx.status = "ok" ixpfx.save() - def save(self, create_ixlan=True, **kwargs): - """ - When an internet exchange is saved, make sure the ixlan for it - exists - - Keyword Argument(s): - - - create_ixlan (`bool`=True): if True and the ix is missing - it's ixlan, create it - """ - r = super(InternetExchange, self).save(**kwargs) - - if not self.ixlan and create_ixlan: - ixlan = IXLan(ix=self, status=self.status, mtu=0) - - # ixlan id will be set to match ix id in ixlan's clean() - # call - ixlan.clean() - - ixlan.save() - - return r - def validate_phonenumbers(self): self.tech_phone = validate_phonenumber(self.tech_phone, self.country.code) self.policy_phone = validate_phonenumber(self.policy_phone, self.country.code) @@ -1512,11 +1478,6 @@ class IXLan(pdb_models.IXLanBase): Describes a LAN at an exchange """ - # as we are preparing to drop IXLans from the schema, as an interim - # step (#21) we are giving each ix one ixlan with matching ids, so we need - # to have an id field that doesnt automatically increment - id = models.IntegerField(primary_key=True) - ix = models.ForeignKey( InternetExchange, on_delete=models.CASCADE, default=0, related_name="ixlan_set" ) @@ -1537,7 +1498,7 @@ class IXLan(pdb_models.IXLanBase): """ Returns a descriptive label of the ixlan for logging purposes """ - return "ixlan{} {}".format(self.id, self.ix.name) + return "ixlan{} {} {}".format(self.id, self.name, self.ix.name) @classmethod def nsp_namespace_from_id(cls, org_id, ix_id, id): @@ -1605,27 +1566,6 @@ class IXLan(pdb_models.IXLanBase): return True return False - def clean(self): - # id is set and does not match the parent ix id - - if self.id and self.id != self.ix.id: - raise ValidationError({"id": _("IXLan id needs to match parent ix id")}) - - # id is not set (new ixlan) - - if not self.id: - - # ixlan for ix already exists - - if self.ix.ixlan: - raise ValidationError(_("Ixlan for exchange already exists")) - - # enforce correct id moving forward - - self.id = self.ix.id - - return super(IXLan, self).clean() - @reversion.create_revision() def add_netixlan(self, netixlan_info, save=True, save_others=True): """ diff --git a/peeringdb_server/rest.py b/peeringdb_server/rest.py index 8fd7ddc7..b633ba08 100644 --- a/peeringdb_server/rest.py +++ b/peeringdb_server/rest.py @@ -558,7 +558,7 @@ def ref_dict(): return {tag: view.model for tag, view, na in router.registry} -def model_view_set(model, methods=None): +def model_view_set(model): """ shortcut for peeringdb models to generate viewset and register in the API urls """ @@ -593,9 +593,6 @@ def model_view_set(model, methods=None): # create the type viewset_t = type(model + "ViewSet", (ModelViewSet,), clsdict) - if methods: - viewset_t.http_method_names = methods - # register with the rest router for incoming requests ref_tag = model_t.handleref.tag router.register(ref_tag, viewset_t, basename=ref_tag) @@ -606,7 +603,7 @@ def model_view_set(model, methods=None): FacilityViewSet = model_view_set("Facility") InternetExchangeViewSet = model_view_set("InternetExchange") InternetExchangeFacilityViewSet = model_view_set("InternetExchangeFacility") -IXLanViewSet = model_view_set("IXLan", methods=["get", "put"]) +IXLanViewSet = model_view_set("IXLan") IXLanPrefixViewSet = model_view_set("IXLanPrefix") NetworkViewSet = model_view_set("Network") NetworkContactViewSet = model_view_set("NetworkContact") diff --git a/peeringdb_server/serializers.py b/peeringdb_server/serializers.py index 76180086..11453ce5 100644 --- a/peeringdb_server/serializers.py +++ b/peeringdb_server/serializers.py @@ -2005,13 +2005,8 @@ class InternetExchangeSerializer(ModelSerializer): # create ix r = super(InternetExchangeSerializer, self).create(validated_data) - ixlan = r.ixlan - # create ixlan - # if False:# not ixlan: - # ixlan = IXLan(ix=r, status="pending") - # ixlan.clean() - # ixlan.save() + ixlan = IXLan.objects.create(name="Main", ix=r, status="pending") # see if prefix already exists in a deleted state ixpfx = IXLanPrefix.objects.filter(prefix=prefix, status="deleted").first() diff --git a/peeringdb_server/static/20c/twentyc.edit.js b/peeringdb_server/static/20c/twentyc.edit.js index d7a4d2f2..91588a7d 100644 --- a/peeringdb_server/static/20c/twentyc.edit.js +++ b/peeringdb_server/static/20c/twentyc.edit.js @@ -174,7 +174,7 @@ twentyc.editable.action.register( modules = [], targets = 1, changed, - status={"error":false, "data":{}}, + status={"error":false}, i; @@ -182,16 +182,13 @@ twentyc.editable.action.register( targets--; if(error) status.error = true; - - if(data) { - $.extend(status.data, data); - } - if(!targets) { if(!status.error && !me.noToggle) { - container.editable("toggle", { data:status.data }); + if(data) + container.editable("toggle", { data:data }); + else + container.editable("toggle"); } - /* if(!status.error && container.data("edit-always")) { // if container is always toggled to edit mode @@ -215,8 +212,6 @@ twentyc.editable.action.register( var target = twentyc.editable.target.instantiate(container); changed = target.data._changed; - $.extend(status.data, target.data); - // prepare modules container.find("[data-edit-module]"). //editable("filter", { belongs : container }). @@ -249,14 +244,7 @@ twentyc.editable.action.register( } var grouped = container.editable("filter", { grouped : true }).not("[data-edit-module]"); - - grouped.each(function(idx) { - var target = twentyc.editable.target.instantiate($(this)); - $.extend(status.data, target.data); - if(target.data._changed) { - targets += 1 - } - }); + targets += grouped.length; if(changed || container.data("edit-always-submit") == "yes"){ $(target).on("success", function(ev, data) { @@ -270,9 +258,8 @@ twentyc.editable.action.register( // submit main target var result = target.execute(); - } else { + } else dec_targets({}, {}); - } // submit grouped targets diff --git a/peeringdb_server/static/peeringdb.js b/peeringdb_server/static/peeringdb.js index edf0df70..98c495f3 100644 --- a/peeringdb_server/static/peeringdb.js +++ b/peeringdb_server/static/peeringdb.js @@ -252,13 +252,6 @@ PeeringDB.ViewActions = { actions : {} } -PeeringDB.ViewActions.actions.ix_ixf_preview = function(netId) { - $("#ixf-preview-modal").modal("show"); - var preview = new PeeringDB.IXFPreview() - preview.request(netId, $("#ixf-log")); -} - - PeeringDB.ViewActions.actions.net_ixf_preview = function(netId) { $("#ixf-preview-modal").modal("show"); var preview = new PeeringDB.IXFNetPreview() @@ -269,6 +262,7 @@ PeeringDB.ViewActions.actions.net_ixf_postmortem = function(netId) { $("#ixf-postmortem-modal").modal("show"); var postmortem = new PeeringDB.IXFNetPostmortem() postmortem.request(netId, $("#ixf-postmortem")); + } @@ -1093,7 +1087,6 @@ twentyc.editable.target.register( "base" ); - /* * editable api listing module */ @@ -1352,6 +1345,34 @@ twentyc.editable.module.register( }, + // FINALIZERS: IXLAN + + finalize_add_ixlan : function(data, callback, sentData) { + + // we currently do not publish ix-f setting fields on the API + // so we need to set those from sent data + data.ixf_ixp_member_list_url = sentData.ixf_ixp_member_list_url; + data.ixf_ixp_import_enabled = sentData.ixf_ixp_import_enabled; + callback(data); + }, + + + finalize_row_ixlan : function(rowId, row, data) { + row.editable("payload", { + ix_id : data.ix_id + }) + row.data("edit-label", gettext("IXLAN") + ": "+data.name); /// + + var modPrefix = row.find('[data-edit-module="api_listing"]'); + modPrefix.editable("sync"); + modPrefix.editable("toggle"); + + var cmpPrefixAdd = row.find('[data-edit-component="add"]') + cmpPrefixAdd.editable("payload", { + ixlan_id : data.id + }); + }, + // FINALIZERS: IXLAN PREFIX finalize_row_ixpfx : function(rowId, row, data) { diff --git a/peeringdb_server/static/site.css b/peeringdb_server/static/site.css index 8f9ac0e3..132c6b10 100644 --- a/peeringdb_server/static/site.css +++ b/peeringdb_server/static/site.css @@ -660,7 +660,6 @@ table.result { div.list { margin-left: 15px; margin-right: 15px; - margin-top: 15px; } div.list div.header { @@ -681,7 +680,6 @@ div.list h5 { overflow: hidden; text-overflow: ellipsis; text-align: left; - margin-left: -11px; } div.list div.empty-result { diff --git a/peeringdb_server/templates/site/view.html b/peeringdb_server/templates/site/view.html index 8db1d231..364f4b35 100644 --- a/peeringdb_server/templates/site/view.html +++ b/peeringdb_server/templates/site/view.html @@ -63,27 +63,7 @@
{% for row in data.fields %} - {% if row.type == "group" %} -
-
-
{{ row.label }}
-
- -
- {% for payload_row in row.payload %} -
{{ payload_row.value }}
- {% endfor %} -
- - {% elif row.type == "group_end" %} -
- {% endif %} - - {% if not row.value|dont_render and row.type != "group" and row.type != "group_end" %} + {% if not row.value|dont_render %} {% if not row.admin or permissions.can_write %}
diff --git a/peeringdb_server/templates/site/view_exchange_assets.html b/peeringdb_server/templates/site/view_exchange_assets.html index f6da15a3..214b7331 100644 --- a/peeringdb_server/templates/site/view_exchange_assets.html +++ b/peeringdb_server/templates/site/view_exchange_assets.html @@ -26,13 +26,84 @@
+
+
+ {% if permissions.can_delete %} + × + {% endif %} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + {% trans "Enable IX-F Import" %} +
+ +
+
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
-
+
{% if permissions.can_delete %} × {% endif %}
-
+
diff --git a/peeringdb_server/templates/site/view_exchange_bottom.html b/peeringdb_server/templates/site/view_exchange_bottom.html index 871193d0..7ababeb2 100644 --- a/peeringdb_server/templates/site/view_exchange_bottom.html +++ b/peeringdb_server/templates/site/view_exchange_bottom.html @@ -1,81 +1,244 @@ {% load util %} {% load i18n %} - - + +{% if not data.lan_simple_view %}
- -
{% trans "Prefixes" %}
+ data-edit-target="api:ixlan"> +
+
+
LANs
+
+
+ +
+
-
-
{% trans "Protocol" %}
+
+
{% trans "Name" %}
-
-
{% trans "Prefix" %}
+
+
{% trans "DOT1Q" %}
+
+
+
{% trans "MTU" %}
- {% with x=instance.ixlan %} -
+
- {% for prefix in x.ixpfx_set_active_or_pending %} -
+
+
{% trans "No filter matches." %}
+
{% trans "You may filter by" %} {%trans "Name" %} {% trans "or" %} {% trans "dot1q" %}.
+
+ + {% for x in data.ixlans %} +
-
{{ x.id }}
-
{{ prefix.protocol }}
+
{{ x.ix_id }}
-
+
{% if permissions.can_delete %} - × + × {% endif %} - {{ prefix.protocol }} +
+ {{ x.name }} +
-
{{ prefix.prefix }}
+ data-edit-template="check" + data-edit-value="{{ x.dot1q_support }}"> + dot1q_support +
+
+
{{ x.mtu|fallback:"" }}
+
+
+
{{ x.ixf_ixp_member_list_url|fallback:"" }}
+ {% if permissions.can_write %} + + {% endif %} +
+ {% if permissions.can_write %} +
+ + + + {% trans "Enable IX-F Import" %} +
+ {% endif %} +
+ +
+ + {% for prefix in x.ixpfx_set_active_or_pending %} +
+
+
{{ x.id }}
+
{{ prefix.protocol }}
+
+
+ {% if permissions.can_delete %} + × + {% endif %} + {{ prefix.protocol }} +
+
{{ prefix.prefix }}
+
+ {% endfor %} +
+ {% if permissions.can_create %} +
+
+
{{ x.id }}
+
+ +
+
+
+
+
+ +
+
+ + {% endif %} +
+ {% endfor %} +
+ {% if permissions.can_create %}
-
-
{{ x.id }}
-
-
-
-
-
-
- -
+
+
{{ instance.id }}
+
+ +
+
{% trans "Name" %}
+
+
+
+
+
+ +
+
{% trans "DOT1Q" %}
+
+
+
+
+
+ +
+
{% trans "MTU" %}
+
+
+
+
+
+ + +
+
{% trans "IXF Member Export URL" %}
+
+
+
+
+
+ + +
+
{% trans "IXF Import Enabled" %}
+
+
+
+
+
+ + + +
+ {% endif %} - {% endwith %} - + + +
+{% endif %} +
+ + {% endif %} diff --git a/peeringdb_server/views.py b/peeringdb_server/views.py index 8c07f87e..11b2e792 100644 --- a/peeringdb_server/views.py +++ b/peeringdb_server/views.py @@ -1270,57 +1270,48 @@ def view_exchange(request, id): ], } - # IXLAN field group (form) + ixlan_num = data["ixlans"].count() - ixlan = exchange.ixlan + if ixlan_num < 2 and not perms.get("can_edit"): + # if there is less than one LAN connected to this ix + # we want to render a simplified view to read-only + # viewers - data["fields"].extend( - [ - { - "type": "group", - "target": "api:ixlan:update", - "id": ixlan.id, - "label": _("LAN"), - "payload": [{"name": "ix_id", "value": exchange.id},], - }, - { - "type": "flags", - "label": _("DOT1Q"), - "value": [{"name": "dot1q_support", "value": ixlan.dot1q_support}], - }, - { - "type": "number", - "name": "mtu", - "label": _("MTU"), - "value": (ixlan.mtu or 0), - }, - { - "type": "flags", - "label": _("Enable IX-F Import"), - "value": [ + data["lan_simple_view"] = True + + if ixlan_num == 1: + + ixlan = data["ixlans"].first() + + data["fields"].extend( + [ + {"type": "sub", "label": _("LAN")}, { - "name": "ixf_ixp_import_enabled", - "value": ixlan.ixf_ixp_import_enabled, + "type": "number", + "name": "mtu", + "label": _("MTU"), + "value": ixlan.mtu or "", + }, + { + "type": "bool", + "name": "dot1q_support", + "label": _("DOT1Q"), + "value": ixlan.dot1q_support, + }, + ] + ) + + data["fields"].extend( + [ + { + "type": "string", + "name": "prefix_%d" % prefix.id, + "label": _(prefix.protocol), + "value": prefix.prefix, } - ], - "admin": True, - }, - { - "type": "url", - "label": _("IX-F Member Export URL"), - "name": "ixf_ixp_member_list_url", - "value": ixlan.ixf_ixp_member_list_url, - "admin": True, - }, - { - "type": "action", - "label": _("IX-F Import Preview"), - "actions": [{"label": _("Preview"), "action": "ixf_preview",},], - "admin": True, - }, - {"type": "group_end"}, - ] - ) + for prefix in ixlan.ixpfx_set_active + ] + ) return view_component( request, "exchange", data, "Exchange", perms=perms, instance=exchange diff --git a/tests/data/ixf/logs/skip_prefix_mismatch.json b/tests/data/ixf/logs/skip_prefix_mismatch.json index d5d7bff1..ba8821b7 100644 --- a/tests/data/ixf/logs/skip_prefix_mismatch.json +++ b/tests/data/ixf/logs/skip_prefix_mismatch.json @@ -7,8 +7,8 @@ "data": [ { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "ixlan_id": 2, "asn": 2906 }, @@ -17,113 +17,113 @@ }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "195.69.146.250", - "ipaddr6": "2001:7f8:1::a500:2906:2", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:2" }, "action": "ignore", "reason": "Ip addresses (195.69.146.250, 2001:7f8:1::a500:2906:2) do not match any prefix on this ixlan" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "195.69.147.250", - "ipaddr6": "2001:7f8:1::a500:2906:1", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:1" }, "action": "ignore", "reason": "Ip addresses (195.69.147.250, 2001:7f8:1::a500:2906:1) do not match any prefix on this ixlan" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "195.69.147.251", - "ipaddr6": "2001:7f8:1::a500:2906:5", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:5" }, "action": "ignore", "reason": "Ip addresses (195.69.147.251, 2001:7f8:1::a500:2906:5) do not match any prefix on this ixlan" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "195.69.147.253", - "ipaddr6": "", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "" }, "action": "ignore", "reason": "Ip addresses (195.69.147.253, None) do not match any prefix on this ixlan" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "", - "ipaddr6": "2001:7f8:1::a500:2906:6", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:6" }, "action": "ignore", "reason": "Ip addresses (None, 2001:7f8:1::a500:2906:6) do not match any prefix on this ixlan" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 20000, "ipaddr4": "195.69.146.251", - "ipaddr6": "2001:7f8:1::a500:2906:3", "is_rs_peer": true, + "speed": 20000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:3" }, "action": "noop", "reason": "Connection is currently marked as inactive" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "net_id": 1, - "speed": 10000, "ipaddr4": "195.69.146.252", - "ipaddr6": "2001:7f8:1::a500:2906:4", "is_rs_peer": true, + "speed": 10000, "ixlan_id": 2, - "asn": 2906 + "asn": 2906, + "ipaddr6": "2001:7f8:1::a500:2906:4" }, "action": "noop", "reason": "Connection is currently marked as inactive" }, { "peer": { - "ix_id": 2, - "ix_name": "Test Exchange 2", + "ix_name": "Test Exchange", + "ix_id": 1, "ixlan_id": 2, "asn": 2906 }, diff --git a/tests/test_admin.py b/tests/test_admin.py index c702873a..ff072a1f 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -68,7 +68,9 @@ class AdminTests(TestCase): cls.admin_user.save() # set up some ixlans - cls.entities["ixlan"] = [ix.ixlan for ix in cls.entities["ix"]] + cls.entities["ixlan"] = [ + models.IXLan.objects.create(ix=ix, status="ok") for ix in cls.entities["ix"] + ] # set up a prefix cls.entities["ixpfx"] = [ diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 89a96d66..eac50901 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -101,7 +101,9 @@ class AdvancedSearchExportTest(ClientCase): ] # create ixlans - cls.ixlan = [ix.ixlan for ix in cls.ix] + cls.ixlan = [ + IXLan.objects.create(ix=cls.ix[i - 1], status="ok") for i in entity_count + ] # create netixlans cls.netixlan = [ diff --git a/tests/test_ixf_member_import.py b/tests/test_ixf_member_import.py index de4cf060..eb1c9781 100644 --- a/tests/test_ixf_member_import.py +++ b/tests/test_ixf_member_import.py @@ -73,17 +73,15 @@ class JsonMembersListTestCase(ClientCase): cls.entities["ix"] = [ InternetExchange.objects.create( name="Test Exchange", org=cls.entities["org"][0], status="ok" - ), - InternetExchange.objects.create( - name="Test Exchange 2", org=cls.entities["org"][0], status="ok" - ), - InternetExchange.objects.create( - name="Test Exchange 3", org=cls.entities["org"][0], status="ok" - ), + ) ] # create ixlan(s) - cls.entities["ixlan"] = [ix.ixlan for ix in cls.entities["ix"]] + cls.entities["ixlan"] = [ + IXLan.objects.create(ix=cls.entities["ix"][0], status="ok"), + IXLan.objects.create(ix=cls.entities["ix"][0], status="ok"), + IXLan.objects.create(ix=cls.entities["ix"][0], status="ok"), + ] # create ixlan prefix(s) cls.entities["ixpfx"] = [ @@ -679,8 +677,7 @@ class TestImportPreview(ClientCase): name="Test IX", status="ok", org=cls.org ) - cls.ixlan = cls.ix.ixlan - + cls.ixlan = IXLan.objects.create(status="ok", ix=cls.ix) IXLanPrefix.objects.create( ixlan=cls.ixlan, status="ok", prefix="195.69.144.0/22", protocol="IPv4" ) diff --git a/tests/test_undelete.py b/tests/test_undelete.py index f9672020..6c75f803 100644 --- a/tests/test_undelete.py +++ b/tests/test_undelete.py @@ -103,3 +103,45 @@ class TestUndelete(ClientCase): assert self.ixlan_a.status == "ok" assert self.ixlan_a.netixlan_set_active.count() == 0 + + def test_undelete_ixlan_netixlan_dupe_same_ix(self): + ixlan_c = REFTAG_MAP["ixlan"].objects.create(ix=self.ix_a, status="ok") + netixlan_a = self.ixlan_a.netixlan_set.first() + self.ixlan_a.delete() + netixlan_c = REFTAG_MAP["netixlan"].objects.create( + asn=self.net_a.asn, + ixlan=ixlan_c, + status="ok", + ipaddr4=netixlan_a.ipaddr4, + network=self.net_a, + speed=100, + ) + + assert ixlan_c.netixlan_set_active.count() == 1 + + self._undelete(self.ixlan_a) + + assert self.ixlan_a.status == "ok" + assert self.ixlan_a.netixlan_set_active.count() == 1 + assert ixlan_c.netixlan_set_active.count() == 0 + + def test_undelete_ixlan_netixlan_dupe_same_ix_ipv6(self): + ixlan_c = REFTAG_MAP["ixlan"].objects.create(ix=self.ix_a, status="ok") + netixlan_a = self.ixlan_a.netixlan_set.first() + self.ixlan_a.delete() + netixlan_c = REFTAG_MAP["netixlan"].objects.create( + asn=self.net_a.asn, + ixlan=ixlan_c, + status="ok", + ipaddr6=netixlan_a.ipaddr6, + network=self.net_a, + speed=100, + ) + + assert ixlan_c.netixlan_set_active.count() == 1 + + self._undelete(self.ixlan_a) + + assert self.ixlan_a.status == "ok" + assert self.ixlan_a.netixlan_set_active.count() == 1 + assert ixlan_c.netixlan_set_active.count() == 0 diff --git a/tests/test_validators.py b/tests/test_validators.py index 36a19f7e..0c6766de 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -139,7 +139,7 @@ def test_validate_prefixlen(): def test_validate_prefix_overlap(): org = Organization.objects.create(name="Test org", status="ok") ix = InternetExchange.objects.create(name="Text exchange", status="ok", org=org) - ixlan = ix.ixlan + ixlan = IXLan.objects.create(ix=ix, status="ok") pfx1 = IXLanPrefix.objects.create( ixlan=ixlan,