Пример #1
0
 def init_plugin(self):
     super(ConduitsPlugin, self).init_plugin()
     self.add_view("api_plugin_%s_get_neighbors" % self.name,
                   self.api_get_neighbors,
                   url="^(?P<id>[0-9a-f]{24})/plugin/%s/get_neighbors/$" %
                   self.name,
                   method=["GET"])
     self.add_view(
         "api_plugin_%s_create_ducts" % self.name,
         self.api_create_ducts,
         url="^(?P<id>[0-9a-f]{24})/plugin/%s/$" % self.name,
         method=["POST"],
         validate={
             "ducts":
             DictListParameter(
                 attrs={
                     "target":
                     DocumentParameter(Object),
                     "project_distance":
                     FloatParameter(),
                     "conduits":
                     DictListParameter(
                         attrs={
                             "id": DocumentParameter(Object,
                                                     required=False),
                             "n": IntParameter(),
                             "x": IntParameter(),
                             "y": IntParameter(),
                             "status": BooleanParameter()
                         })
                 })
         })
     #
     self.conduits_model = ObjectModel.objects.filter(
         name=self.CONDUITS_MODEL).first()
Пример #2
0
 def __init__(self, model):
     self.model = model
     self.app = None
     self.pk_field_name = "id"
     # Prepare field converters
     self.clean_fields = self.clean_fields.copy()  # name -> Parameter
     for name, f in self.model._fields.items():
         if isinstance(f, BooleanField):
             self.clean_fields[name] = BooleanParameter()
         elif isinstance(f, IntField):
             self.clean_fields[name] = IntParameter()
         elif isinstance(f, PlainReferenceField):
             self.clean_fields[name] = DocumentParameter(f.document_type)
     #
     if not self.query_fields:
         self.query_fields = [
             "%s__%s" % (n, self.query_condition)
             for n, f in self.model._fields.items()
             if f.unique and isinstance(f, StringField)
         ]
     # Find field_* and populate custom fields
     self.custom_fields = {}
     for fn in [n for n in dir(self) if n.startswith("field_")]:
         h = getattr(self, fn)
         if callable(h):
             self.custom_fields[fn[6:]] = h
Пример #3
0
 def api_action_group_edit(self, request):
     validator = DictParameter(
         attrs={"ids": ListOfParameter(element=DocumentParameter(self.model), convert=True)}
     )
     rv = self.deserialize(request.body)
     try:
         v = validator.clean(rv)
     except InterfaceTypeError as e:
         self.logger.info("Bad request: %r (%s)", request.body, e)
         return self.render_json(
             {"status": False, "message": "Bad request", "traceback": str(e)},
             status=self.BAD_REQUEST,
         )
     objects = v["ids"]
     del v["ids"]
     try:
         v = self.clean(v)
     except ValueError as e:
         return self.render_json(
             {"status": False, "message": "Bad request", "traceback": str(e)},
             status=self.BAD_REQUEST,
         )
     for o in objects:
         for p in v:
             setattr(o, p, v[p])
         o.save()
     return "%d records has been updated" % len(objects)
Пример #4
0
class UnknownModelApplication(ExtDocApplication):
    """
    UnknownModel application
    """

    title = _("Unknown Models")
    menu = _("Unknown Models")
    model = UnknownModel

    query_condition = "icontains"
    query_fields = [
        "vendor", "managed_object", "platform", "part_no", "description"
    ]

    @view(
        url="^actions/remove/$",
        method=["POST"],
        access="launch",
        api=True,
        validate={
            "ids":
            ListOfParameter(element=DocumentParameter(UnknownModel),
                            convert=True)
        },
    )
    def api_action_run_discovery(self, request, ids):
        UnknownModel.objects.filter(id__in=[x.id for x in ids])
        return _("Cleaned")
Пример #5
0
class ConnectionRuleApplication(ExtDocApplication):
    """
    ConnectionRule application
    """

    title = _("Connection Rules")
    menu = [_("Setup"), _("Connection Rules")]
    model = ConnectionRule
    query_fields = ["name__icontains", "description__icontains"]

    @view(
        url="^actions/json/$",
        method=["POST"],
        access="read",
        validate={
            "ids":
            ListOfParameter(element=DocumentParameter(ConnectionRule),
                            convert=True)
        },
        api=True,
    )
    def api_action_json(self, request, ids):
        r = [o.json_data for o in ids]
        s = to_json(r, order=["name", "description"])
        return {"data": s}
Пример #6
0
    def init_plugin(self):
        super().init_plugin()
        self.add_view(
            "api_plugin_%s_get_layer" % self.name,
            self.api_get_layer,
            url=r"^plugin/%s/layers/(?P<layer>\S+)/$" % self.name,
            method=["GET"],
        )
        self.add_view(
            "api_plugin_%s_object_data" % self.name,
            self.api_object_data,
            url="^(?P<id>[0-9a-f]{24})/plugin/%s/object_data/$" % self.name,
            method=["GET"],
        )
        self.add_view(
            "api_plugin_%s_set_geopoint" % self.name,
            self.api_set_geopoint,
            url="^(?P<id>[0-9a-f]{24})/plugin/%s/set_geopoint/$" % self.name,
            method=["POST"],
            validate={
                "srid": StringParameter(),
                "x": FloatParameter(),
                "y": FloatParameter()
            },
        )
        self.add_view(
            "api_plugin_%s_set_layer_visibility" % self.name,
            self.api_set_layer_visibility,
            url="^plugin/%s/layer_visibility/$" % self.name,
            method=["POST"],
            validate={
                "layer": StringParameter(),
                "status": BooleanParameter()
            },
        )

        self.add_view(
            "api_plugin_%s_create" % self.name,
            self.api_create,
            url="^plugin/%s/$" % self.name,
            method=["POST"],
            validate={
                "model": DocumentParameter(ObjectModel),
                "name": UnicodeParameter(),
                "srid": StringParameter(),
                "x": FloatParameter(),
                "y": FloatParameter(),
            },
        )
Пример #7
0
class ObjectModelApplication(ExtDocApplication):
    """
    ObjectModel application
    """
    title = "Object Models"
    menu = "Setup | Object Models"
    model = ObjectModel
    parent_model = DocCategory
    parent_field = "parent"
    query_fields = [
        "name__icontains", "description__icontains", "data__asset__part_no",
        "data__asset__order_part_no", "uuid"
    ]

    def clean(self, data):
        if "data" in data:
            data["data"] = ModelInterface.clean_data(data["data"])
        if "plugins" in data and data["plugins"]:
            data["plugins"] = [
                x.strip() for x in data["plugins"].split(",") if x.strip()
            ]
        else:
            data["plugins"] = None
        return super(ObjectModelApplication, self).clean(data)

    def cleaned_query(self, q):
        if "is_container" in q:
            q["data__container__container"] = True
            q["name__ne"] = "Root"
            del q["is_container"]
        return super(ObjectModelApplication, self).cleaned_query(q)

    @view(url="^(?P<id>[0-9a-f]{24})/compatible/$",
          method=["GET"],
          access="read",
          api=True)
    def api_compatible(self, request, id):
        o = self.get_object_or_404(ObjectModel, id=id)
        # Connections
        r = []
        for c in o.connections:
            # Find compatible objects
            proposals = []
            for t, n in o.get_connection_proposals(c.name):
                m = ObjectModel.objects.filter(id=t).first()
                mc = m.get_model_connection(n)
                proposals += [{
                    "model": m.name,
                    "model_description": m.description,
                    "name": n,
                    "description": mc.description,
                    "gender": mc.gender
                }]
            #
            if (r and r[-1]["direction"] == c.direction
                    and r[-1]["gender"] == c.gender
                    and r[-1]["connections"] == proposals):
                r[-1]["names"] += [{
                    "name": c.name,
                    "description": c.description
                }]
            else:
                r += [{
                    "names": [{
                        "name": c.name,
                        "description": c.description
                    }],
                    "direction": c.direction,
                    "gender": c.gender,
                    "connections": proposals
                }]
        # Crossing
        # @todo: Count splitter interface
        rc = []
        for c in o.connections:
            if c.cross:
                rc += [{"y": c.name, "x": c.cross, "v": "1"}]
        return {"connections": r, "crossing": rc}

    @view(url="^actions/json/$",
          method=["POST"],
          access="read",
          validate={
              "ids":
              ListOfParameter(element=DocumentParameter(ObjectModel),
                              convert=True)
          },
          api=True)
    def api_action_json(self, request, ids):
        r = [o.json_data for o in ids]
        s = to_json(r, order=["name", "vendor__code", "description"])
        return {"data": s}
Пример #8
0
 def __init__(self, *args, **kwargs):
     super(ExtDocApplication, self).__init__(*args, **kwargs)
     self.pk = "id"  # @todo: detect properly
     self.has_uuid = False
     # Prepare field converters
     self.clean_fields = self.clean_fields.copy()  # name -> Parameter
     for name, f in six.iteritems(self.model._fields):
         if isinstance(f, BooleanField):
             self.clean_fields[name] = BooleanParameter()
         elif isinstance(f, GeoPointField):
             self.clean_fields[name] = GeoPointParameter()
         elif isinstance(f, ForeignKeyField):
             self.clean_fields[f.name] = ModelParameter(f.document_type, required=f.required)
         elif isinstance(f, ListField):
             if isinstance(f.field, EmbeddedDocumentField):
                 self.clean_fields[f.name] = ListOfParameter(
                     element=EmbeddedDocumentParameter(f.field.document_type)
                 )
         elif isinstance(f, ReferenceField):
             dt = f.document_type_obj
             if dt == "self":
                 dt = self.model
             self.clean_fields[f.name] = DocumentParameter(dt, required=f.required)
         if f.primary_key:
             self.pk = name
         if name == "uuid":
             self.has_uuid = True
     #
     if not self.query_fields:
         self.query_fields = [
             "%s__%s" % (n, self.query_condition)
             for n, f in six.iteritems(self.model._fields)
             if f.unique and isinstance(f, StringField)
         ]
     self.unique_fields = [n for n, f in six.iteritems(self.model._fields) if f.unique]
     # Install JSON API call when necessary
     self.json_collection = self.model._meta.get("json_collection")
     if (
         self.has_uuid
         and hasattr(self.model, "to_json")
         and not hasattr(self, "api_to_json")
         and not hasattr(self, "api_json")
     ):
         self.add_view(
             "api_json",
             self._api_to_json,
             url=r"^(?P<id>[0-9a-f]{24})/json/$",
             method=["GET"],
             access="read",
             api=True,
         )
         self.add_view(
             "api_share_info",
             self._api_share_info,
             url=r"^(?P<id>[0-9a-f]{24})/share_info/$",
             method=["GET"],
             access="read",
             api=True,
         )
     if self.json_collection:
         self.bulk_fields += [self._bulk_field_is_builtin]
     # Find field_* and populate custom fields
     self.custom_fields = {}
     for fn in [n for n in dir(self) if n.startswith("field_")]:
         h = getattr(self, fn)
         if callable(h):
             self.custom_fields[fn[6:]] = h
Пример #9
0
class ValidationPolicySettingsApplication(ExtDocApplication):
    """
    ValidationPolicySettings application
    """

    title = _("Validation Policy Settings")
    model = ValidationPolicySettings

    MODEL_SCOPES = {
        "sa.ManagedObject": 2,
        "sa.ManagedObjectProfile": 2,
        "inv.Interface": 2,
        "inv.InterfaceProfile": 2,
    }

    @view(
        "^(?P<model_id>[^/]+)/(?P<object_id>[^/]+)/settings/$",
        method=["GET"],
        access="read",
        api=True,
    )
    def api_get_settings(self, request, model_id, object_id):
        if model_id not in self.MODEL_SCOPES:
            return self.response_not_found("Invalid model")
        o = ValidationPolicySettings.objects.filter(
            model_id=model_id, object_id=object_id).first()
        if o:
            # Policy settings
            return [{
                "policy": str(p.policy.id),
                "policy__label": p.policy.name,
                "is_active": p.is_active,
            } for p in o.policies]
        else:
            return {}

    @view(
        "^(?P<model_id>[^/]+)/(?P<object_id>[^/]+)/settings/$",
        method=["POST"],
        access="read",
        api=True,
        validate={
            "policies":
            DictListParameter(
                attrs={
                    "policy": DocumentParameter(ValidationPolicy),
                    "is_active": BooleanParameter(),
                })
        },
    )
    def api_save_settings(self, request, model_id, object_id, policies):
        def save_settings(o):
            o.save()
            return self.response({"status": True}, self.OK)

        o = ValidationPolicySettings.objects.filter(
            model_id=model_id, object_id=object_id).first()
        seen = set()
        ps = []
        for p in policies:
            if p["policy"].id in seen:
                continue
            ps += [
                ValidationPolicyItem(policy=p["policy"],
                                     is_active=p["is_active"])
            ]
            seen.add(p["policy"].id)
        if o:
            o.policies = ps
        else:
            o = ValidationPolicySettings(model_id=model_id,
                                         object_id=object_id,
                                         policies=ps)
        self.submit_slow_op(request, save_settings, o)
Пример #10
0
class InterfaceAppplication(ExtApplication):
    """
    inv.interface application
    """

    title = _("Interfaces")
    menu = _("Interfaces")

    mrt_config = {
        "get_mac": {
            "map_script": "get_mac_address_table",
            "timeout": config.script.timeout,
            "access": "get_mac",
        }
    }

    implied_permissions = {
        "get_mac": [
            "inv:inv:read",
            "inv:interface:view",
            "sa:managedobject:lookup",
            "sa:managedobject:read",
        ]
    }

    @view(url=r"^(?P<managed_object>\d+)/$",
          method=["GET"],
          access="view",
          api=True)
    def api_get_interfaces(self, request, managed_object):
        """
        GET interfaces
        :param managed_object:
        :return:
        """
        def sorted_iname(s):
            return list(sorted(s, key=lambda x: alnum_key(x["name"])))

        def get_style(i):
            profile = i.profile
            if profile:
                try:
                    return style_cache[profile.id]
                except KeyError:
                    pass
                if profile.style:
                    s = profile.style.css_class_name
                else:
                    s = ""
                style_cache[profile.id] = s
                return s
            else:
                return ""

        def get_link(i):
            link = i.link
            if not link:
                return None
            if link.is_ptp:
                # ptp
                o = link.other_ptp(i)
                label = "%s:%s" % (o.managed_object.name, o.name)
            elif link.is_lag:
                # unresolved LAG
                o = [
                    ii for ii in link.other(i)
                    if ii.managed_object.id != i.managed_object.id
                ]
                label = "LAG %s: %s" % (o[0].managed_object.name, ", ".join(
                    ii.name for ii in o))
            else:
                # Broadcast
                label = ", ".join("%s:%s" % (ii.managed_object.name, ii.name)
                                  for ii in link.other(i))
            return {"id": str(link.id), "label": label}

        # Get object
        o = self.get_object_or_404(ManagedObject, id=int(managed_object))
        if not o.has_access(request.user):
            return self.response_forbidden("Permission denied")
        # Physical interfaces
        # @todo: proper ordering
        default_state = ResourceState.get_default()
        style_cache = {}  # profile_id -> css_style
        l1 = [{
            "id":
            str(i.id),
            "name":
            i.name,
            "description":
            i.description,
            "mac":
            i.mac,
            "ifindex":
            i.ifindex,
            "lag":
            (i.aggregated_interface.name if i.aggregated_interface else ""),
            "link":
            get_link(i),
            "profile":
            str(i.profile.id) if i.profile else None,
            "profile__label":
            smart_text(i.profile) if i.profile else None,
            "enabled_protocols":
            i.enabled_protocols,
            "project":
            i.project.id if i.project else None,
            "project__label":
            smart_text(i.project) if i.project else None,
            "state":
            i.state.id if i.state else default_state.id,
            "state__label":
            smart_text(i.state if i.state else default_state),
            "vc_domain":
            i.vc_domain.id if i.vc_domain else None,
            "vc_domain__label":
            smart_text(i.vc_domain) if i.vc_domain else None,
            "row_class":
            get_style(i),
        } for i in Interface.objects.filter(managed_object=o.id,
                                            type="physical")]
        # LAG
        lag = [{
            "id":
            str(i.id),
            "name":
            i.name,
            "description":
            i.description,
            "members": [
                j.name
                for j in Interface.objects.filter(managed_object=o.id,
                                                  aggregated_interface=i.id)
            ],
            "profile":
            str(i.profile.id) if i.profile else None,
            "profile__label":
            smart_text(i.profile) if i.profile else None,
            "enabled_protocols":
            i.enabled_protocols,
            "project":
            i.project.id if i.project else None,
            "project__label":
            smart_text(i.project) if i.project else None,
            "state":
            i.state.id if i.state else default_state.id,
            "state__label":
            smart_text(i.state if i.state else default_state),
            "vc_domain":
            i.vc_domain.id if i.vc_domain else None,
            "vc_domain__label":
            smart_text(i.vc_domain) if i.vc_domain else None,
            "row_class":
            get_style(i),
        } for i in Interface.objects.filter(managed_object=o.id,
                                            type="aggregated")]
        # L2 interfaces
        l2 = [{
            "name": i.name,
            "description": i.description,
            "untagged_vlan": i.untagged_vlan,
            "tagged_vlans": i.tagged_vlans,
        } for i in SubInterface.objects.filter(managed_object=o.id,
                                               enabled_afi="BRIDGE")]
        # L3 interfaces
        q = Q(enabled_afi="IPv4") | Q(enabled_afi="IPv6")
        l3 = [{
            "name":
            i.name,
            "description":
            i.description,
            "ipv4_addresses":
            i.ipv4_addresses,
            "ipv6_addresses":
            i.ipv6_addresses,
            "enabled_protocols":
            i.enabled_protocols,
            "vlan":
            i.vlan_ids,
            "vrf":
            i.forwarding_instance.name if i.forwarding_instance else "",
        } for i in SubInterface.objects.filter(managed_object=o.id).filter(q)]
        return {
            "l1": sorted_iname(l1),
            "lag": sorted_iname(lag),
            "l2": sorted_iname(l2),
            "l3": sorted_iname(l3),
        }

    @view(
        url=r"^link/$",
        method=["POST"],
        validate={
            "type": StringParameter(choices=["ptp"]),
            "interfaces":
            ListOfParameter(element=DocumentParameter(Interface)),
        },
        access="link",
        api=True,
    )
    def api_link(self, request, type, interfaces):
        if type == "ptp":
            if len(interfaces) == 2:
                interfaces[0].link_ptp(interfaces[1])
                return {"status": True}
            else:
                raise ValueError("Invalid interfaces length")
        return {"status": False}

    @view(url=r"^unlink/(?P<iface_id>[0-9a-f]{24})/$",
          method=["POST"],
          access="link",
          api=True)
    def api_unlink(self, request, iface_id):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        try:
            i.unlink()
            return {"status": True, "msg": "Unlinked"}
        except ValueError as why:
            return {"status": False, "msg": str(why)}

    @view(url=r"^unlinked/(?P<object_id>\d+)/$",
          method=["GET"],
          access="link",
          api=True)
    def api_unlinked(self, request, object_id):
        def get_label(i):
            if i.description:
                return "%s (%s)" % (i.name, i.description)
            else:
                return i.name

        o = self.get_object_or_404(ManagedObject, id=int(object_id))
        r = [{
            "id": str(i.id),
            "label": get_label(i)
        } for i in Interface.objects.filter(managed_object=o.id,
                                            type="physical").order_by("name")
             if not i.link]
        return list(sorted(r, key=lambda x: alnum_key(x["label"])))

    @view(
        url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_profile/$",
        validate={"profile": DocumentParameter(InterfaceProfile)},
        method=["POST"],
        access="profile",
        api=True,
    )
    def api_change_profile(self, request, iface_id, profile):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        if i.profile != profile:
            i.profile = profile
            i.profile_locked = True
            i.save()
        return True

    @view(
        url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_state/$",
        validate={"state": ModelParameter(ResourceState)},
        method=["POST"],
        access="profile",
        api=True,
    )
    def api_change_state(self, request, iface_id, state):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        if i.state != state:
            i.state = state
            i.save()
        return True

    @view(
        url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_project/$",
        validate={"project": ModelParameter(Project, required=False)},
        method=["POST"],
        access="profile",
        api=True,
    )
    def api_change_project(self, request, iface_id, project):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        if i.project != project:
            i.project = project
            i.save()
        return True

    @view(
        url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_vc_domain/$",
        validate={"vc_domain": ModelParameter(VCDomain, required=False)},
        method=["POST"],
        access="profile",
        api=True,
    )
    def api_change_vc_domain(self, request, iface_id, vc_domain):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        if i.vc_domain != vc_domain:
            i.vc_domain = vc_domain
            i.save()
        return True
Пример #11
0
class MetricSettingsApplication(ExtDocApplication):
    """
    MetricSettings application
    """
    title = "Metric Settings"
    model = MetricSettings

    @view("^(?P<model_id>[^/]+)/(?P<object_id>[^/]+)/settings/$",
          method=["GET"],
          access="read",
          api=True)
    def api_get_settings(self, request, model_id, object_id):
        o = MetricSettings.objects.filter(model_id=model_id,
                                          object_id=object_id).first()
        if o:
            return [{
                "metric_set": str(ms.metric_set.id),
                "metric_set__label": ms.metric_set.name,
                "is_active": ms.is_active
            } for ms in o.metric_sets]
        else:
            return []

    @view("^(?P<model_id>[^/]+)/(?P<object_id>[^/]+)/settings/$",
          method=["POST"],
          access="read",
          api=True,
          validate={
              "metric_sets":
              DictListParameter(
                  attrs={
                      "metric_set": DocumentParameter(MetricSet),
                      "is_active": BooleanParameter()
                  })
          })
    def api_save_settings(self, request, model_id, object_id, metric_sets):
        def save_settings(o):
            o.save()
            return self.response({"status": True}, self.OK)

        o = MetricSettings.objects.filter(model_id=model_id,
                                          object_id=object_id).first()
        seen = set()
        mset = []
        for ms in metric_sets:
            if ms["metric_set"].id in seen:
                continue
            mset += [
                MetricSettingsItem(metric_set=ms["metric_set"],
                                   is_active=ms["is_active"])
            ]
            seen.add(ms["metric_set"].id)
        if o:
            o.metric_sets = mset
        else:
            o = MetricSettings(model_id=model_id,
                               object_id=object_id,
                               metric_sets=mset)
        self.submit_slow_op(request, save_settings, o)

    @view("^(?P<model_id>[^/]+)/(?P<object_id>[^/]+)/effective/trace/$",
          method=["GET"],
          access="read",
          api=True)
    def api_trace_effective(self, request, model_id, object_id):
        o = MetricSettings(model_id=model_id, object_id=object_id).get_object()
        if not o:
            return self.response_not_found()
        r = []
        for es in MetricSettings.get_effective_settings(o,
                                                        trace=True,
                                                        recursive=True):
            for m in es.metrics:
                r += [{
                    "metric": m.metric or None,
                    "metric_type": m.metric_type.name,
                    "is_active": es.is_active,
                    "probe": es.probe.name if es.probe else None,
                    "interval": es.interval if es.interval else None,
                    "thresholds": m.thresholds,
                    "handler": es.handler,
                    "config": es.config,
                    "errors": es.errors,
                    "traces": es.traces
                }]
        return r
Пример #12
0
        def get_label(i):
            if i.description:
                return "%s (%s)" % (i.name, i.description)
            else:
                return i.name

        o = self.get_object_or_404(ManagedObject, id=int(object_id))
        r = [{"id": str(i.id), "label": get_label(i)}
            for i in Interface.objects.filter(managed_object=o.id,
                                        type="physical").order_by("name")
            if not i.link]
        return sorted(r, key=lambda x: split_alnum(x["label"]))

    @view(url="^l1/(?P<iface_id>[0-9a-f]{24})/change_profile/$",
        validate={
            "profile": DocumentParameter(InterfaceProfile)
        },
        method=["POST"], access="profile", api=True)
    def api_change_profile(self, request, iface_id, profile):
        i = Interface.objects.filter(id=iface_id).first()
        if not i:
            return self.response_not_found()
        if i.profile != profile:
            i.profile = profile
            i.profile_locked = True
            i.save()
        return True

    @view(url="^l1/(?P<iface_id>[0-9a-f]{24})/change_state/$",
        validate={
            "state": ModelParameter(ResourceState)