Asterisk и не только. Виртуальные файловые системы. Шаг назад или два вперед? +12


Описывая участие в проекте по модернизации VoIP оператора связи Часть 1 и Часть 2, одной из задач, которая выпала из поля зрения, было создание унифицированного инструмента для визуализации и мониторинга работы сервера Asterisk. По сути, после выхода из данного проекта, навязчивая идея привести отображение информации Asterisk к более удобному виду вылилась в проект создания прототипа унифицированной виртуальной файловой системы, объединяющей возможности всех разрозненных инструментов доступных в Asterisk.


Думаю что многие из администраторов, которые имели дело с Asterisk, зачастую удивлялись тому количеству различных команд, при помощи которых из Asterisk можно получать данные. Речь пойдёт об учётных записях для абонентских устройств, пользователях для аутентификации, каналах, а также о нестандартном применении виртуальных файловых систем.


Остановимся подробнее на различных методах получения данных.


Первый самый распространённый.


Запрос списка пиров
asterisk-macomnet*CLI> sip show peers
Name/username             Host                                    Dyn Forcerport Comedia    ACL Port     Status      Description                      Realtime
6001                      (Unspecified)                            D  Auto (No)  No             0        Unmonitored          
person/person         (Unspecified)                            D  Auto (No)  No             0        UNKNOWN              
3 sip peers [Monitored: 0 online, 1 offline Unmonitored: 1 online, 1 offline]

Запрос данных о пире
asterisk-macomnet*CLI> sip show peer 6001

  * Name       : 6001
  Description  :
  Realtime peer: No
  Secret       : <Set>
  MD5Secret    : <Not set>
  Remote Secret: <Not set>
  Context      : web
  Record On feature : automon
  Record Off feature : automon
  Subscr.Cont. : <Not set>
  Language     : ru
  Tonezone     : <Not set>
  AMA flags    : Unknown
  Transfer mode: open
  CallingPres  : Presentation Allowed, Not Screened
  Callgroup    :
  Pickupgroup  :
  Named Callgr :
  Nam. Pickupgr:
  MOH Suggest  :
  Mailbox      :
  VM Extension : asterisk
  LastMsgsSent : 0/0
  Call limit   : 0
  Max forwards : 0
  Dynamic      : Yes
  Callerid     : "6001" <6001>
  MaxCallBR    : 384 kbps
  Expire       : -1
  Insecure     : no
  Force rport  : Auto (No)
  Symmetric RTP: No
  ACL          : No
  DirectMedACL : No
  T.38 support : No
  T.38 EC mode : Unknown
  T.38 MaxDtgrm: 4294967295
  DirectMedia  : No
  PromiscRedir : No
  User=Phone   : No
  Video Support: No
  Text Support : No
  Ign SDP ver  : No
  Trust RPID   : No
  Send RPID    : No
  Path support : No
  Path         : N/A
  TrustIDOutbnd: Legacy
  Subscriptions: Yes
  Overlap dial : No
  DTMFmode     : rfc2833
  Timer T1     : 500
  Timer B      : 32000
  ToHost       :
  Addr->IP     : (null)
  Defaddr->IP  : (null)
  Prim.Transp. : UDP
  Allowed.Trsp : UDP,WS
  Def. Username:
  SIP Options  : (none)
  Codecs       : (gsm|g729|ulaw|alaw)
  Auto-Framing : No
  Status       : Unmonitored
  Useragent    :
  Reg. Contact :
  Qualify Freq : 60000 ms
  Keepalive    : 0 ms
  Sess-Timers  : Accept
  Sess-Refresh : uas
  Sess-Expires : 1800 secs
  Min-Sess     : 90 secs
  RTP Engine   : asterisk
  Parkinglot   :
  Use Reason   : No
  Encryption   : Yes

Запрос данных о пользователях
asterisk-macomnet*CLI> sip show users
Username                   Secret           Accountcode      Def.Context      ACL  Forcerport
6001                       MegaPass12345                         web              No   No
person                      E346fz8Vam                        users_context    No   No

asterisk-macomnet*CLI> sip show user 6001

  * Name       : 6001
  Secret       : <Set>
  MD5Secret    : <Not set>
  Context      : web
  Language     : ru
  AMA flags    : Unknown
  Tonezone     : <Not set>
  Transfer mode: open
  MaxCallBR    : 384 kbps
  CallingPres  : Presentation Allowed, Not Screened
  Call limit   : 0
  Callgroup    :
  Pickupgroup  :
  Named Callgr :
  Nam. Pickupgr:
  Callerid     : "6001" <6001>
  ACL          : No
  Sess-Timers  : Accept
  Sess-Refresh : uas
  Sess-Expires : 1800 secs
  Sess-Min-SE  : 90 secs
  RTP Engine   : asterisk
  Auto-Framing:  No

Не правда ли знакомые команды? И многие ими пользуются почти ежедневно. Ни для кого наверное не является секретом, что в Asterisk есть дерево объектов. Доступ к нему предоставляется через провайдеров. Информация в дереве полнее чем мы видели раньше.


asterisk-macomnet*CLI> data show providers
/asterisk/channel/iax2/peers (get) [chan_iax2.c]
/asterisk/channel/iax2/users (get) [chan_iax2.c]
/asterisk/channel/dahdi/version (get) [chan_dahdi.c]
/asterisk/channel/dahdi/status (get) [chan_dahdi.c]
/asterisk/channel/dahdi/channels (get) [chan_dahdi.c]
/asterisk/channel/sip/peers (get) [chan_sip.c]
/asterisk/core/hints (get) [pbx.c]
/asterisk/core/channels (get) [channel.c]
/asterisk/core/channeltypes (get) [channel.c]
/asterisk/application/queue/list (get) [app_queue.c]

Доступ к дереву объектов, позволяет иметь полную информацию о каждом из них.


Доступ к дереву объектов, позволяет иметь полную информацию о каждом из них.
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers
peers
  peer
      vmexten: "asterisk"
      is_realtime: False
      amaflags
          text: "Unknown"
          value: 0
      transports: "UDP,WS"
      inuse: 0
      timer_t1: 500
      callingpres
          text: "Presentation Allowed, Not Screened"
          value: 0
      unsolicited_mailbox: ""
      host_dynamic: True
      subscribecontext: ""
      useragent: ""
      rtptimeout: 0
      cid_num: "6001"
      mohinterpret: "default"
      parkinglot: ""
      mwi_from: ""
      call_limit: 0
      fullcontact: ""
      rtpholdtimeout: 0
      qualifyfreq: 60000
      maxms: 0
      rtpkeepalive: 0
      allowtransfer
          text: "open"
          value: 0
      regexten: ""
      onhold: 0
      ringing: 0
      fromdomain: ""
      timer_b: 32000
      fromuser: ""
      username: ""
      secret: "MegaPass12345"
      accountcode: ""
      t38_maxdatagram: -1
      remotesecret: ""
      md5secret: ""
      maxcallbitrate: 384
      mohsuggest: ""
      lastms: 0
      sipoptions
          sec_agree: False
          histinfo: False
          join: False
          resource-priority: False
          precondition: False
          eventlist: False
          outbound: False
          recipient-list-invite: False
          early-session: False
          recipient-list-subscribe: False
          sdp-anat: False
          100rel: False
          replaces: False
          from-change: False
          replace: False
          gruu: False
          tdialog: False
          privacy: False
          norefersub: False
          timer: False
          path: False
          pref: False
      tohost: ""
      autoframing: False
      name: "6001"
      cid_name: "6001"
      language: "ru"
      context: "web"
      description: ""
      type: "friend"
      engine: "asterisk"
      codecs
          codec
              frame_length: 33
              samplespersecond: 8000
              name: "gsm"
              description: "GSM"
          codec
              frame_length: 10
              samplespersecond: 8000
              name: "g729"
              description: "G.729A"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "ulaw"
              description: "G.711 u-law"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "alaw"
              description: "G.711 a-law"
  peer
      vmexten: "asterisk"
      is_realtime: False
      amaflags
          text: "Unknown"
          value: 0
      transports: "UDP,WS"
      inuse: 0
      timer_t1: 500
      callingpres
          text: "Presentation Allowed, Not Screened"
          value: 0
      unsolicited_mailbox: ""
      host_dynamic: True
      subscribecontext: ""
      useragent: ""
      rtptimeout: 0
      cid_num: "person"
      mohinterpret: "default"
      parkinglot: ""
      mwi_from: ""
      call_limit: 0
      fullcontact: ""
      rtpholdtimeout: 0
      qualifyfreq: 60000
      maxms: 2000
      rtpkeepalive: 0
      allowtransfer
          text: "open"
          value: 0
      regexten: ""
      onhold: 0
      ringing: 0
      fromdomain: ""
      timer_b: 32000
      fromuser: ""
      username: "person"
      secret: "E346fz8Vam"
      accountcode: ""
      t38_maxdatagram: -1
      remotesecret: ""
      md5secret: ""
      maxcallbitrate: 384
      mohsuggest: ""
      lastms: 0
      sipoptions
          sec_agree: False
          histinfo: False
          join: False
          resource-priority: False
          precondition: False
          eventlist: False
          outbound: False
          recipient-list-invite: False
          early-session: False
          recipient-list-subscribe: False
          sdp-anat: False
          100rel: False
          replaces: False
          from-change: False
          replace: False
          gruu: False
          tdialog: False
          privacy: False
          norefersub: False
          timer: False
          path: False
          pref: False
      tohost: ""
      autoframing: False
      name: "person"
      cid_name: "person"
      language: "en"
      context: "users_context"
      description: ""
      type: "friend"
      engine: "asterisk"
      codecs
          codec
              frame_length: 33
              samplespersecond: 8000
              name: "gsm"
              description: "GSM"
          codec
              frame_length: 10
              samplespersecond: 8000
              name: "g729"
              description: "G.729A"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "ulaw"
              description: "G.711 u-law"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "alaw"
              description: "G.711 a-law"

Наверное мало кто знает, из-за отсутствия нормальной документации на команду "data get", но в ней есть поиск и группировка полей.


Поиск по имени или по параметру пира
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers peers/peer/name=6001
или
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers peers/peer/secret=MegaPass12345
peers
  peer
      vmexten: "asterisk"
      is_realtime: False
      amaflags
          text: "Unknown"
          value: 0
      transports: "UDP,WS"
      inuse: 0
      timer_t1: 500
      callingpres
          text: "Presentation Allowed, Not Screened"
          value: 0
      unsolicited_mailbox: ""
      host_dynamic: True
      subscribecontext: ""
      useragent: ""
      rtptimeout: 0
      cid_num: "6001"
      mohinterpret: "default"
      parkinglot: ""
      mwi_from: ""
      call_limit: 0
      fullcontact: ""
      rtpholdtimeout: 0
      qualifyfreq: 60000
      maxms: 0
      rtpkeepalive: 0
      allowtransfer
          text: "open"
          value: 0
      regexten: ""
      onhold: 0
      ringing: 0
      fromdomain: ""
      timer_b: 32000
      fromuser: ""
      username: ""
      secret: "MegaPass12345"
      accountcode: ""
      t38_maxdatagram: -1
      remotesecret: ""
      md5secret: ""
      maxcallbitrate: 384
      mohsuggest: ""
      lastms: 0
      sipoptions
          sec_agree: False
          histinfo: False
          join: False
          resource-priority: False
          precondition: False
          eventlist: False
          outbound: False
          recipient-list-invite: False
          early-session: False
          recipient-list-subscribe: False
          sdp-anat: False
          100rel: False
          replaces: False
          from-change: False
          replace: False
          gruu: False
          tdialog: False
          privacy: False
          norefersub: False
          timer: False
          path: False
          pref: False
      tohost: ""
      autoframing: False
      name: "6001"
      cid_name: "6001"
      language: "ru"
      context: "web"
      description: ""
      type: "friend"
      engine: "asterisk"
      codecs
          codec
              frame_length: 33
              samplespersecond: 8000
              name: "gsm"
              description: "GSM"
          codec
              frame_length: 10
              samplespersecond: 8000
              name: "g729"
              description: "G.729A"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "ulaw"
              description: "G.711 u-law"
          codec
              frame_length: 80
              samplespersecond: 8000
              name: "alaw"
              description: "G.711 a-law"

Запрос данных из дерева поддерживает фильтрацию параметров. При этом параметр поиска должен иметь значение "1=1", любое другое условие поиска описанное выше игнорируется автоматически.


asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers 1=1 peers/peer/cid_num,peers/peer/name,peers/peer/username,peers/peer/secret,peers/peer/lastms,peers/peer/fullcontact
peers
  peer
      cid_num: "6001"
      fullcontact: "sip:6001@192.168.200.247:5060"
      username: ""
      secret: "MegaPass12345"
      lastms: 0
      name: "6001"
  peer
      cid_num: "person"
      fullcontact: ""
      username: "person"
      secret: "E346fz8Vam"
      lastms: 0
      name: "person"

Доступ к дереву это очень удобно, но дерево по умолчанию содержит только записи жёстко прописанные в конфигурационных файлах либо записи кешированных realtime подключений. Для получения полноценного дерева необходимо загрузить из базы данных все параметры всех realtime подключений.


Отображение данных realtime пиров производится командой:


asterisk-macomnet*CLI> sip show peer <name> load
Usage: sip show peer <name> [load]
       Shows all details on one SIP peer and the current status.
       Option "load" forces lookup of peer in realtime storage.

К сожалению для того, чтобы загрузить данные, нужно знать что загружать.


asterisk-macomnet*CLI> realtime load sippeers name zhecka
Usage: realtime load <family> <colmatch> <value>
       Prints out a list of variables using the RealTime driver.
       You must supply a family name, a column to match on, and a value to match to.

Как мы здесь видим, для загрузки данных realtime подключения необходимо также знать что грузить. Но есть "грязный хак". Параметры "colmatch" и "value" передаются в sql провайдер "как есть". Поэтому можно сделать запрос вида "realtime load sippeers 1 1" и получить все данные за один запрос.


realtime load sippeers 1 1
asterisk-macomnet*CLI> realtime load sippeers 1 1
                   Column Name  Column Value
          --------------------  --------------------
                     dtlssetup  actpass
                            id  1325
                          name  zhecka
                        ipaddr
                          port  0
                    regseconds  0
                   defaultuser  zhecka
                   fullcontact
                     regserver
                     useragent
                        lastms  0
                          host  dynamic
                          type  friend
                       context  web
                        permit
                          deny
                        secret  zhecka
                     md5secret
                  remotesecret
                     transport  udp,tcp
                      dtmfmode
                   directmedia  no
                           nat
                     callgroup
                   pickupgroup
                      language
                      disallow
                         allow
                      insecure
                     trustrpid
                progressinband
                  promiscredir
                 useclientcode
                   accountcode
                        setvar
                      callerid
                      amaflags
                   callcounter
                     busylevel
                  allowoverlap
                allowsubscribe
                  videosupport
                maxcallbitrate
             rfc2833compensate
                       mailbox
                session-timers
               session-expires
                 session-minse
             session-refresher
            t38pt_usertpsource
                      regexten
                    fromdomain
                      fromuser
                       qualify
                     defaultip
                    rtptimeout
                rtpholdtimeout
                      sendrpid
                 outboundproxy
             callbackextension
                       timert1
                        timerb
                   qualifyfreq
                  constantssrc
                 contactpermit
                   contactdeny
                   usereqphone
                   textsupport
                     faxdetect
                      buggymwi
                          auth
                      fullname
                     trunkname
                    cid_number
                   callingpres
                  mohinterpret
                    mohsuggest
                    parkinglot
                  hasvoicemail
                  subscribemwi
                       vmexten
                   autoframing
                  rtpkeepalive
                    call-limit
               g726nonstandard
              ignoresdpversion
                 allowtransfer
                       dynamic
                          path
                   supportpath
                    encryption  no
                          avpf  yes
                     force_avp  yes
                    icesupport  yes
                    dtlsenable  yes
                    dtlsverify  fingerprint
                  dtlscertfile  /usr/local/share/asterisk/keys/cert.crt
                dtlsprivatekey  /usr/local/share/asterisk/keys/private.pem
                    dtlscafile  /usr/local/share/asterisk/keys/ca.crt
                     dtlssetup  actpass
                            id  1327
                          name  zhecka1
                        ipaddr
                          port
                    regseconds
                   defaultuser  zhecka1
                   fullcontact
                     regserver
                     useragent
                        lastms
                          host  dynamic
                          type  friend
                       context  web
                        permit
                          deny
                        secret
                     md5secret
                  remotesecret
                     transport  ws
                      dtmfmode
                   directmedia  no
                           nat
                     callgroup
                   pickupgroup
                      language
                      disallow
                         allow
                      insecure
                     trustrpid
                progressinband
                  promiscredir
                 useclientcode
                   accountcode
                        setvar
                      callerid
                      amaflags
                   callcounter
                     busylevel
                  allowoverlap
                allowsubscribe
                  videosupport
                maxcallbitrate
             rfc2833compensate
                       mailbox
                session-timers
               session-expires
                 session-minse
             session-refresher
            t38pt_usertpsource
                      regexten
                    fromdomain
                      fromuser
                       qualify
                     defaultip
                    rtptimeout
                rtpholdtimeout
                      sendrpid
                 outboundproxy
             callbackextension
                       timert1
                        timerb
                   qualifyfreq
                  constantssrc
                 contactpermit
                   contactdeny
                   usereqphone
                   textsupport
                     faxdetect
                      buggymwi
                          auth
                      fullname
                     trunkname
                    cid_number
                   callingpres
                  mohinterpret
                    mohsuggest
                    parkinglot
                  hasvoicemail
                  subscribemwi
                       vmexten
                   autoframing
                  rtpkeepalive
                    call-limit
               g726nonstandard
              ignoresdpversion
                 allowtransfer
                       dynamic
                          path
                   supportpath
                    encryption  yes
                          avpf  yes
                     force_avp  yes
                    icesupport  yes
                    dtlsenable  yes
                    dtlsverify  fingerprint
                  dtlscertfile  /usr/local/share/asterisk/keys/cert.crt
                dtlsprivatekey  /usr/local/share/asterisk/keys/private.pem
                    dtlscafile  /usr/local/share/asterisk/keys/ca.crt
                     dtlssetup  actpass

Данный запрос покажет все realtime пиры, которые имеются в базе данных.
Далее необходимо по имени каждого пира выполнить команду


sip show peer zhecka load
asterisk-macomnet*CLI> sip show peer zhecka load

  * Name       : zhecka
  Description  :
  Realtime peer: Yes, cached
  Secret       : <Set>
  MD5Secret    : <Not set>
  Remote Secret: <Not set>
  Context      : web
  Record On feature : automon
  Record Off feature : automon
  Subscr.Cont. : <Not set>
  Language     : ru
  Tonezone     : <Not set>
  AMA flags    : Unknown
  Transfer mode: open
  CallingPres  : Presentation Allowed, Not Screened
  Callgroup    :
  Pickupgroup  :
  Named Callgr :
  Nam. Pickupgr:
  MOH Suggest  :
  Mailbox      :
  VM Extension : asterisk
  LastMsgsSent : 0/0
  Call limit   : 0
  Max forwards : 0
  Dynamic      : Yes
  Callerid     : "" <>
  MaxCallBR    : 384 kbps
  Expire       : 120
  Insecure     : no
  Force rport  : Auto (No)
  Symmetric RTP: No
  ACL          : No
  DirectMedACL : No
  T.38 support : No
  T.38 EC mode : Unknown
  T.38 MaxDtgrm: 4294967295
  DirectMedia  : No
  PromiscRedir : No
  User=Phone   : No
  Video Support: No
  Text Support : No
  Ign SDP ver  : No
  Trust RPID   : No
  Send RPID    : No
  Path support : No
  Path         : N/A
  TrustIDOutbnd: Legacy
  Subscriptions: Yes
  Overlap dial : No
  DTMFmode     : rfc2833
  Timer T1     : 500
  Timer B      : 32000
  ToHost       :
  Addr->IP     : (null)
  Defaddr->IP  : (null)
  Prim.Transp. : UDP
  Allowed.Trsp : UDP,TCP
  Def. Username: zhecka
  SIP Options  : (none)
  Codecs       : (ulaw|alaw|gsm|h263)
  Auto-Framing : No
  Status       : Unmonitored
  Useragent    :
  Reg. Contact :
  Qualify Freq : 60000 ms
  Keepalive    : 0 ms
  Sess-Timers  : Accept
  Sess-Refresh : uas
  Sess-Expires : 1800 secs
  Min-Sess     : 90 secs
  RTP Engine   : asterisk
  Parkinglot   :
  Use Reason   : No
  Encryption   : No

После этих операций во внутреннем дереве данных появится запись realtime пира "zhecka", находящегося в данный момент в "offline"


asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers 1=1 peers/peer/cid_num,peers/peer/name,peers/peer/username,peers/peer/secret,peers/peer/lastms,peers/peer/fullcontact
peers
  peer
      cid_num: ""
      fullcontact: ""
      username: "zhecka"
      secret: "zhecka"
      lastms: 4294967295
      name: "zhecka"
  peer
      cid_num: "6001"
      fullcontact: ""
      username: ""
      secret: "MegaPass12345"
      lastms: 0
      name: "6001"
  peer
      cid_num: "person"
      fullcontact: ""
      username: "person"
      secret: "E346fz8Vam"
      lastms: 0
      name: "person"

Как видно из представленной последовательности, для получения данных необходимо проделать несколько различных операций для каждого имеющегося в realtime базе пира.


Наверное многие из Вас скажут: "Есть же REST! используй его!"
Отвечу: "Да. REST есть. Но он слабоват и не даёт нужной информации"
Вывод REST запроса http://localhost:8088/ari/endpoints/SIP


[
  {
    "technology": "SIP",
    "resource": "person",
    "state": "unknown",
    "channel_ids": []
  },
  {
    "technology": "SIP",
    "resource": "mtrf",
    "state": "unknown",
    "channel_ids": []
  },
  {
    "technology": "SIP",
    "resource": "6001",
    "state": "unknown",
    "channel_ids": []
  }
]

В выводе к сожалению отсутствуют нужные поля и для их добавления необходимо переписать или дописать REST модуль.


Итак, что же предлагается? А предлагается перенести дерево Asterisk в файловую систему на манер devfs/procfs и отслеживать все настройки в одном месте.


Выглядит это примерно вот так.


# tree /mnt
/mnt
+-- config -> /usr/local/etc/asterisk
+-- peers
¦   +-- iax2
¦   ¦   +-- corbina
¦   ¦   ¦   L-- full
¦   ¦   L-- msm
¦   ¦       L-- full
¦   L-- sip
¦       +-- _offline
¦       ¦   L-- mtrf -> ../mtrf
¦       +-- _online
¦       L-- mtrf
¦           +-- codecs
¦           +-- contexts
¦           +-- credentials
¦           +-- full
¦           L-- huntgroups
L-- users
    +-- iax2
    ¦   L-- macomnet
    ¦       L-- full
    +-- sipfriends
    ¦   +-- 6001
    ¦   ¦   +-- codecs
    ¦   ¦   +-- contexts
    ¦   ¦   +-- credentials
    ¦   ¦   +-- full
    ¦   ¦   L-- huntgroups
    ¦   +-- _offline
    ¦   ¦   +-- 6001 -> ../6001
    ¦   ¦   L-- person -> ../person
    ¦   +-- _online
    ¦   ¦   L-- zhecka -> ../zhecka
    ¦   +-- person
    ¦   ¦   +-- codecs
    ¦   ¦   +-- contexts
    ¦   ¦   +-- credentials
    ¦   ¦   +-- full
    ¦   ¦   L-- huntgroups
    ¦   L-- zhecka
    ¦       +-- codecs
    ¦       +-- contexts
    ¦       +-- credentials
    ¦       +-- full
    ¦       L-- huntgroups
    L-- sipusers
        +-- _offline
        ¦   L-- zhecka1 -> ../zhecka1
        +-- _online
        L-- zhecka1
            +-- codecs
            +-- contexts
            +-- credentials
            +-- full
            L-- huntgroups

Это реальный слепок файловой системы тестового сервера Asterisk. Так как мы не ограничены в форматировании выдаваемых данных, то выдача параметров для подключения будет выглядеть следующим образом.


# cat /mnt/users/sipfriends/_online/zhecka/credentials
name=zhecka
username=zhecka
secret=zhecka
md5secret=
fromuser=
fromdomain=
cid_name=
remotesecret=
fullcontact=sip:zhecka@10.6.207.135:64802;transport=udp;rinstance=d42eec40501a7c4d
useragent=Bria release 2.5 RC4 stamp 47242
regexten=
transports=UDP,TCP

Или контексты


# cat /mnt/users/sipfriends/_online/zhecka/contexts
context=web
subscribecontext=

Все эти данные получаются в реальном режиме времени, ну или с минимальной задержкой при обработке Asterisk Events, во время отключения/подключения устройства.


Как это работает? AstFS написана на языке Python и использует библиотеку Fuse.


Библиотека Fuse позволяет на каждый соответствующий event (open, readdir, getattr, close и т.д.), приходящий от файловой системы, вызвать процедуру обрабатывающую входящие и формирующую конечные данные пользователя.


Так как библиотека Fuse как и большинство event based библиотек имеет внутренний loop, то передать ей данные с внешней программы довольно тяжело. Необходим общий объект или pipe для работы.


С точки зрения использования TCP/UDP socket pipe или чего-то похожего, в случае отказа сервера или демона обеспечивающего доставку данных, запрос к файловой системе бесконтрольно повиснет. Опять же из-за собственного внутреннего цикла библиотека не может быть корректно внедрена в асинхронные фреймворки типа twisted(у меня не получилось по крайней мере). Попытка завести всё через Threading вызвала кучу разных проблем и от этой идеи пришлось отказаться. Вызов соединения из процедур напрямую к Asterisk вызывал дичайший оверхед, т.к. на каждый "чих" необходимо было открывать новое соединение к AMI.


Единственным более менее нормальным решением было разделение процесса обслуживающего запросы к файловой системе и процесса получающего данные из Asterisk используя multiprocessing. У данного компонента имеется возможность контроля за порождаемыми процессами, а также имеются объекты, которые можно "шарить" между процессами.


Таким образом система состоит из трёх процессов:


  • Manager
  • FuseFS
  • Asterisk Event Handler

Между ними "шарится" общий словарь упакованный в JSON. Дело в том, что общие объекты имеют структуру отличную от python структур и с ними не совместимы. При обновлении данных из Asterisk, данный словарь распаковывается, обновляется и запаковывается обратно.


Соответственно процесс обслуживающий запросы из файловой системы при необходимости распаковывает словарь и формирует конечные данные для пользователя.


Исходный код AstFS содержит все необходимые классы и пример для создания виртуальной файловой системы. Код конечно далёк от идеала, но будет модифицироваться в лучшую сторону по возможности(есть коментарии на русском).


Основной упор был сделан на гибкость структуры файловой системы.


Под гибкостью подразумевается, что в качестве обработчика ветки дерева или файла может использоваться либо класс либо функция либо просто статические данные.


Определение дерева довольно простое:


    root = {
        'helpfilefunc': [fsstruct.stat_info['file'], fusefscore.returnhelp],
        'link_to_tmp': [fsstruct.stat_info['link'], '/tmp'],
        'staticfile': [fsstruct.stat_info['file'], 'contents of staticfile'],
        'customchmodfile': [{'st_mode': 0x8000 | int('0600', 8), 'st_uid': 0, 'st_gid': 0}, 'contents of customchmodfile'],
        'nulldir': {},
        'nullfile': [{}, ''],
        'demotreeclass': fsstruct.BasicTree('/demotreeclass', mpd, ''),
        'tree': {'peer': [{'st_mode': 0x8000 | int('0644', 8)}, 'contents of peer'], 'friend': {}, 'user': {}}
}

# tree -ghpu /mnt/
/mnt/
+-- [-rw------- root     wheel      27]  customchmodfile
+-- [drwxr-xr-x asterisk asterisk  512]  demotreeclass
¦   +-- [-rw-r--r-- asterisk asterisk   12]  help
¦   L-- [-rw-r--r-- asterisk asterisk   12]  info
+-- [-rw-r--r-- asterisk asterisk   33]  helpfilefunc
+-- [lrwxr-xr-x asterisk asterisk    4]  link_to_tmp -> /tmp
+-- [drwxr-xr-x asterisk asterisk  512]  nulldir
+-- [-rw-r--r-- asterisk asterisk    0]  nullfile
+-- [-rw-r--r-- asterisk asterisk   22]  staticfile
L-- [drwxr-xr-x asterisk asterisk  512]  tree
    +-- [drwxr-xr-x asterisk asterisk  512]  friend
    +-- [-rw-r--r-- asterisk asterisk   16]  peer
    L-- [drwxr-xr-x asterisk asterisk  512]  user

6 directories, 7 files

Если нужно определить файл, то просто задаётся массив (list) состоящий из двух параметров. Первый это тип файла и права доступа к нему, второй это либо статическое содержимое файла, либо имя функции возвращающей содержимое файла. Если нужно определить директорию, то просто определяется словарь (dict) содержащий в свою очередь либо список файлов, либо список директорий. При определении директории в которой находится динамически изменяемая структура, можно задать подкласс, который будет обрабатывать все запросы файловой системы.


По сути для базового функционала в классе достаточно описать 4 метода:


  • getattr
  • read
  • readdir
  • readlink

Остановимся поподробнее об особенностях реализации.


getattr вызывается для определения типа файла или директории, возвращает только атрибуты! Никакой другой информации из данного метода не получается.


readdir вызывается при открытии директории и запросе файлов, возвращает только список файлов находящихся в директории без атрибутов!


При обычном вызове списка файлов


# ls /mnt
customchmodfile demotreeclass   helpfilefunc    link_to_tmp     nulldir         nullfile        staticfile      tree

происходит следующая последовательность


Запрос списка файлов
2017-03-14 13:13:04,559 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,559 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}

2017-03-14 13:13:04,560 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:13:04,560 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:13:04,561 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,561 - DEBUG - [FuseFSCore.readdir] Path /:0. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}

2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.readdir] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.readdir] SrcTree: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}, DirTree: <type 'dict'>:{'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}

  • через getattr запрашивается наличие файла или пути "/"
  • функция обрабатывающая дерево файлов returntree возвращает содержимое запрошенной ветки дерева с добавлением элемента '__dirstat__', который в последующих функциях либо выдаётся в качестве ответа на getattr, либо удаляется если запрашивается список файлов в директории.
  • на выходе из getattr удаляется список файлов и системе возвращаются только атрибуты директории.
  • запрашивается размер блока на томе через statfs
  • система запрашивает содержимое директории "/"
  • returntree возвращает список файлов с атрибутами директории.
  • readdir убирает атрибуты директории из списка и отдаёт системе только список файлов.

Попробуем открыть файл


Открытие файла
2017-03-14 13:26:45,137 - DEBUG - [FuseFSCore.open] /staticfile:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,138 - DEBUG - [FuseFSCore.getattr] path /staticfile fh None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,139 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,139 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']

2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] Found data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /staticfile is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,141 - DEBUG - [FuseFSCore.read] read file /staticfile:4096:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,141 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']

2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.read] dirtree data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.read] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.read] read file /staticfile:4096:22. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']

2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.read] dirtree data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.read] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.truncate] /staticfile:22:None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.getattr] path /staticfile fh None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']

2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] Found data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /staticfile is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.release] /staticfile:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}

Последовательность похожа.


  • через open указывается имя файла
  • через getattr запрашиваются атрибуты файла
  • производится проверка файла на чтение с нулевым размером читаемого блока, если всё хорошо — производится чтение блока из файла равного размеру блока файловой системы или если размер файла меньше, то размеру файла.
  • файл закрывается и освобождается

Так работает статическая структура. А что же с динамической ?


Откроем директорию /mnt/demotreeclass, обработчик которой ссылается на класс
fsstruct.BasicTree.


Открытие директории с обработчиком
2017-03-14 13:29:16,819 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,819 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:29:16,819 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:29:16,820 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:29:16,820 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:29:16,820 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:29:16,821 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:29:16,821 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Path /demotreeclass:0. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:29:16,822 - DEBUG - [BasicTree._readdir] path len 1, path ['']
2017-03-14 13:29:16,822 - DEBUG - [BasicTree._readdir] return full tree {'info': {}, 'help': {}}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'info': {}, 'help': {}}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] SrcTree: <astfs.fsstruct.BasicTree object at 0x8058fe210>, DirTree: <type 'dict'>:{'info': {}, 'help': {}}

Происходит всё тоже самое, что и при открытии статической директории, только при обходе дерева объектов в структуре директории, обработчик видит, что ссылка на директорию — это класс и передаёт управление ему. Далее класс сам в зависимости от запрашиваемой операции формирует ответ и отдаёт его основному процессу.


Сложнее всего дело обстоит если кто-то запрашивает команду


# ls -al /mnt/demotreeclass
total 2
drwxr-xr-x  1 asterisk  asterisk  512 14 мар 11:10 .
drwxr-xr-x  9 asterisk  asterisk  512 14 мар 11:10 ..
-rw-r--r--  1 asterisk  asterisk   12 14 мар 11:10 help
-rw-r--r--  1 asterisk  asterisk   12 14 мар 11:10 info

Детальный запрос содержимого директории
2017-03-14 13:40:46,539 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,540 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,542 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,542 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,542 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,542 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,543 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.readdir] Path /demotreeclass:0. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,544 - DEBUG - [FuseFSCore.readdir] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,544 - DEBUG - [BasicTree._readdir] path len 1, path ['']
2017-03-14 13:40:46,544 - DEBUG - [BasicTree._readdir] return full tree {'info': {}, 'help': {}}
2017-03-14 13:40:46,544 - DEBUG - [FuseFSCore.readdir] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'info': {}, 'help': {}}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.readdir] SrcTree: <astfs.fsstruct.BasicTree object at 0x8058fe210>, DirTree: <type 'dict'>:{'info': {}, 'help': {}}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,545 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,546 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,546 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,547 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,547 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}

2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}

2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] path /demotreeclass/info fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass/info
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getattr] started for path info
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getattr] path len 1, path ['info']
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 0, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}, <bound method BasicTree._getfile of <astfs.fsstruct.BasicTree object at 0x8058fe210>>]
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getfile] started for path info
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass/info is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] path /demotreeclass/help fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass/help
2017-03-14 13:40:46,551 - DEBUG - [BasicTree._getattr] started for path help
2017-03-14 13:40:46,551 - DEBUG - [BasicTree._getattr] path len 1, path ['help']
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 0, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}, <bound method BasicTree._getfile of <astfs.fsstruct.BasicTree object at 0x8058fe210>>]
2017-03-14 13:40:46,552 - DEBUG - [BasicTree._getfile] started for path help
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass/help is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,553 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}

В данном случае происходит рекурсивный запрос по всем объектам внутри директории и если их довольно много, то это может создать довольно существенную нагрузку на систему, особенно если включен дебаг.


За счёт того, что чтение файлов производится по возвращаемым системой атрибутам, возникает необходимость постоянного пересчёта размера файлов при запросах. Неверно выданный размер приводит к "обрезанию" файла либо ошибке файловой системы.


При обращении к ссылке на ресурс(symlink)


# ls /mnt/link_to_tmp
.ICE-unix                       aguilera.ulaw                   crontab.B0fGYRPx73~             macroform-the_simplicity.wav    mycachedir                      pymp-Ri0pXJ                     pymp-eysf7M                     socket
.X11-unix                       cachedir                        crontab.SyEoUZMExj~             manolo_camp-morning_coffee.ulaw mysql.sock                      pymp-SZfCWa                     pymp-hH6kLC                     treecache
.XIM-unix                       crontab.1yo8gxs5HE~             errlog                          manolo_camp-morning_coffee.wav  pymp-6oVKsj

Запрос перехода по symlink
2017-03-14 13:47:55,593 - DEBUG - [FuseFSCore.readlink] path: /link_to_tmp. Context: {'gid': 0, 'pid': 99341, 'uid': 0}
2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']

2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.readlink] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.readlink] path: /link_to_tmp. Context: {'gid': 0, 'pid': 99341, 'uid': 0}
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']

2017-03-14 13:47:55,596 - DEBUG - [FuseFSCore.readlink] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']

Происходят всё те же операции:


  • через getattr запрашиваются атрибуты объекта
  • система "видит", что объект является символьной ссылкой и вызывает функцию readlink
  • readlink отдаёт в обычном текстовом виде адрес ресурса на который нужно перенаправить вызов.

Так в чём же красота решения?


Красота решения в том, что на любую ветку дерева можно навесить абсолютно любую логику. Скажем Вы хотите видеть любимый сайт торрентов в файловом виде у себя в системе. Вы просто создаёте класс, который на запрос данных директории "дёргает" трекер и выдаёт Вам список имеющихся торентов в виде списка файлов. При открытии или копировании нужного файла, класс просто "дёргает" нужный файл с трекера и отдаёт его Вам.
Или скажем у вас есть сервер с динамически строящимися отчётами. Вам нет необходимости хранить все отчёты на локальной файловой системе, т.к. они есть в базе. Просто создаётся класс, который смотрит в базу и при запросе выдаёт необходимую информацию о наличии отчётов. Или при запросе отчёта в режиме реального времени строит его.
Или у вас есть логи демонов, которые нужно обрабатывать реалтайм. Можно конечно повесить хендлер, который будет отслеживать изменение лога, или сделать pipe для отправки лога в какой-то скрипт. С помощью виртуальной файловой системы с помощью спецкласса Вы можете писать логи в базу напрямую если это необходимо, при этом уже в подготовленном виде.


Еще одно применение такого плана файловых систем это изоляция данных пользователей друг от друга. При обращении к файлу находящемуся в виртуальной файловой системе в процедуры передаётся переменное окружение в виде номера процесса, uid и gid пользователя. На основе этих данных можно формировать различное содержимое для одного и того же файла, т.е. указатель на файл один, а содержимое для всех разное, скажем файл с набором паролей для конкретного пользователя зашифрованный ключём этого пользователя. Речь конечно о многопользовательских системах, где на одном сервере работает куча народу.


Есть конечно и явный минус заключающийся в недостаточной скорости работы при большой вложенности, но чем-то всё равно приходится жертвовать.


Так что же всё-таки использование виртуальных файловых систем — это шаг назад или два вперед ?


PS: если кто пропустил ссылку на проект на github, вот она


© Aborche 2017
Aborche

-->


К сожалению, не доступен сервер mySQL