Lnmpy

Elvis's Blog

几个活用vim粘贴板的配置命令

追求更高的效率,那么总是会去折腾一些开发工具的快捷操作或者hack了,而其中vim的操作实在是存在各种hack.或许下面的几个你就没有怎么用过:

(尽管是搜索运行之类的操作,但不用退出vim,也没有必要动用鼠标,够hack吧)

先温习一下vim的剪贴板:

  1. vim有12个粘贴板,分别是0、1、2、…、9、a、“、+;用:reg命令可以查看各个粘贴板里的内容.
  2. 在vim中简单用y只是复制到”(双引号)粘贴板里,同样用p粘贴的也是这个粘贴板里的内容.
  3. 在编辑/命令模式下,Ctrl+R+粘贴板id,就可以粘贴相应的内容

在shell中运行光标所在的行:

需求:

在用vim编写shell脚本(哪怕是博客)的过程中,有时候需要运行一两行命令.
很多人可能是用鼠标复制一下,然后再退出或者在vim中加载子shell,再粘贴运行…

在.vimrc中添加(我的leader映射成,):

nmap <leader>e <ESC>:exec ':!'.getline('.')<CR>

然后,光标移动到那特定的一行,按下,e,就可以直接在运行那一行的内容.

Demo:

在vim中运行当前行:

需求:
只能运行完整的一行,好像显得很不够灵活呢,有没有可以让我先在visual模式下选中一段内容再运行呢?

在.vimrc中添加(我的leader映射成,):

vmap <leader>e <ESC>:exec ':!'.<C-R>"

那么在visual模式下选中相应内容,就可以快速执行了,注意使用了系统剪贴板.(这里我没有加回车,这样更方便自己来输入特定的参数了).

Demo:

在vim中visual模式下,搜索选中的内容:

需求:
有时候需要搜索一段文字,要么是按下/,再老老实实的输入整个的内容,要么又是要动鼠标的节奏了..

在.vimrc中添加(我的leader映射成,):

vmap // y/<C-R>"<CR>
vmap <leader>/ y/<C-R>"<CR>

那么在visual模式下选中,就可以快速搜索了.

Demo:

PS:

  1. 截图软件是采用ttyrecttygif来生成的.
  2. 相应的vim其实也集成到了我的repo中(没有使用bundle)

Neutron如何与Nova-Compute进行交互

开门见山,最近在研究了一下Neutton的代码,看的过程中也将所了解的内容进行整理,整理内容如下:

Nova-compute如何发出请求

当我执行nova boot的时候,nova-compute是如何执行接下来的操作呢,贴个代码说起来也太罗嗦了,还是直接用一个流程图来说明一下,然后再根据他图来说明一下就比较直观吧

不过看图容易画图难啊,为了表示函数调用层级,我用数字放在方法前面,比如0 methodA -> 1 methodB -> 2 methodC -> 0 methodD. 就表明,methodA里面调用了methodB,methodB里面调用了methodC,然后methodA结束了,开始调用methodD.OK,上图:

nova-compute-build-instance

Openstack毕竟是各个不同的模块组合起来的,上面的流程是通用的, 不管你是用neutron-ovs,nova-network,neutron-ml2,还是libvirt,lxc,hyperv等, 不变的部分它就长这样了.

既然通用的都说完了,那么接下来就到了如何去管理和创建网络资源了.这里我们就采用Neutron-OVS和libvirt来为例说明.

图中其中两个标红的操作,一个post_message_to_create_and_bindplug,就是接下来要说明的重点:Neutron-OVS返回port并绑定,libvirt如何响应并创建相应的网卡设备

Neutron-OVS返回port并绑定

# OVSNeutronPluginV2下的create_port,Neutron-server最后会调用这个方法
def create_port(self, context, port):
    port['port']['status'] = q_const.PORT_STATUS_DOWN
    port_data = port['port']
    session = context.session
    with session.begin(subtransactions=True):
        self._ensure_default_security_group_on_port(context, port)
        sgids = self._get_security_groups_on_port(context, port)
        dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])

        # 创建port
        port = super(OVSNeutronPluginV2, self).create_port(context, port)

        # 绑定port和host, 实际上是执行插入portbindingports表
        self._process_portbindings_create_and_update(context, port_data, port)

        # 绑定port和security, 实际上是执行插入securitygroupportbindings表
        # 作用嘛,肯定就是用来执行port的securitygroup的操作啦
        self._process_port_create_security_group(context, port, sgids)

        # 执行插入extradhcpopts
        # TODO? 这个作用我还不清楚
        self._process_port_create_extra_dhcp_opts(context, port,
                                                    dhcp_opts)
        # 配置db,以允许该机器与外界进行通信,以后会生成iptables
        # http://blog.csdn.net/matt_mao/article/details/19417451
        port[addr_pair.ADDRESS_PAIRS] = (
            self._process_create_allowed_address_pairs(
                context, port,
                port_data.get(addr_pair.ADDRESS_PAIRS)))
    # 通知相关的agent执行security_group的更新,通常就是iptables
    self.notify_security_groups_member_updated(context, port)
    return port

另外,由于OVSNeutronPluginV2的init方法中,存在base_binding_dict这么一个属性

class OVSNeutronPluginV2(...):
   def __init__(self, configfile=None):
        self.base_binding_dict = {
            portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
            portbindings.CAPABILITIES: {
                portbindings.CAP_PORT_FILTER:
                'security-group' in self.supported_extension_aliases}}

在代码中可以看到,其初始化为base_binding_dict['binding:vif_type']='ovs', 在之后的一些代码中会被update到port中.这个执行neutron port-show中可以看到对应的值,同时,在最后的libvirt的plug方法中也是起着判断因素的.

libvirt如何响应并创建相应的网卡设备

这里就直接上代码,逻辑也不是很复杂.

lib_vif_plug

跟下plug的代码就知道了,其实根据配置文件来选择哪一个vif_driver的.在旧版的代码中,你还是有很多选择的,但是在新版的代码里面就只有LibvirtGenericVIFDriver

# 如此的配置在旧版的nova是可行的,不过代码中会有提示说已经废弃了,建议使用GenericVIFDriver
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtHybridOVSBridgeDriver
# 所以还是使用如下的配置
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver

此处就以OVS的创建代码来说明了

def plug_ovs_hybrid(self,instance,vif):
    iface_id = self.get_ovs_interfaceid(vif)
    # 获取了br_name, 以qbr开头
    br_name = self.get_br_name(vif['id'])
    # 获取了veth_pair_name, 以qvb,qvo开头
    v1_name,v2_name = self.get_veth_pair_names(vif['id'])

    # 添加一个qbr 网桥
    if not linux_net.device_exists(br_name):
        utils.execute('brctl','addbr',br_name,run_as_root=True)
        utils.execute('brctl','setfd',br_name,0,run_as_root=True)
        utils.execute('brctl','stp',br_name,'off',run_as_root=True)
        utils.execute('tee',
                      ('/sys/class/net/%s/bridge/multicast_snooping' %
                       br_name),
                      process_input='0',
                      run_as_root=True,
                      check_exit_code=[0,1])

    # 创建添加一个qvo网桥
    if not linux_net.device_exists(v2_name):
        # 将两个veth创建为一个peer-port
        linux_net._create_veth_pair(v1_name,v2_name)
        utils.execute('ip','link','set',br_name,'up',run_as_root=True)
        # 将qvb接口添加到qbr上
        utils.execute('brctl','addif',br_name,v1_name,run_as_root=True)
        # 将接口qvo桥接到br-int上
        # 分别传入的参数为:br-int, qvo, port['id'], port的mac地址, instance-uuid
        linux_net.create_ovs_vif_port(self.get_bridge_name(vif),
                                      v2_name,iface_id,vif['address'],
                                      instance['uuid'])

def _create_veth_pair(dev1_name, dev2_name):
    # 将两个veth创建为一个peer-port
    for dev in [dev1_name, dev2_name]:
        delete_net_dev(dev)

    utils.execute('ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
                  'name', dev2_name, run_as_root=True)
    for dev in [dev1_name, dev2_name]:
        utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
        utils.execute('ip', 'link', 'set', dev, 'promisc', 'on',
                      run_as_root=True)

def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id):
    # 将接口qvo桥接到br-int上
    # 分别传入的参数为:br-int, qvo, port['id'], port的mac地址, instance-uuid
    utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
                  bridge, dev,
                  '--', 'set', 'Interface', dev,
                  'external-ids:iface-id=%s' % iface_id,
                  'external-ids:iface-status=active',
                  'external-ids:attached-mac=%s' % mac,
                  'external-ids:vm-uuid=%s' % instance_id,
                  run_as_root=True)

由代码可以看出,至此,<qbr>(qvb)(qvo)<br-int>就已经连接上了
至于虚机是如何与<qbr>连上的,这个就是在virt内部做的了,执行以下的命令, 其中的source字段是你提供的qbr, tap则是virt生成的.

virsh domiflist <instance-id>

到这里可以看出,从一个nova-compute到neutron到libvrit的各个网络信息的处理交互. 逻辑相对来说还是比较清晰的。

终于整理完了之后,感觉变成了nova-compute的源码分析了。。。

Neutron-Server启动流程分析

Neutron-Server启动流程分析

正常的来看,应该是先看nova源码的。一方面网上资料也比较丰富,一方面也是更具有代表性。所以我也就不从头再来,浪费太多的精力去描述像router,deploy等库的使用了
比如臭蛋的两篇博客就写得挺详细的,结合代码看一下,倒也可以很快速的了解

Nova Service启动

Openstack Paste Deploy介绍

deploy加载配置定位到具体的类

/etc/neutron/api-paste.ini,有这么一行,直接指明了哪个类作为router启动

paste.app_factory = neutron.api.v2.router:APIRouter.factory

实例化neutron/api/v2/router.py中的APIRouter

class APIRouter(wsgi.Router):
    # 一个工厂类方法
    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)

    # 真正调用的实例化方法
    def __init__(self, **local_config):
        ...
        #获取NeutornManage的core_plugin,这个定义在/etc/neutron/neutron.conf,比如我的是
        #core_plugin = neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2
        plugin = manager.NeutronManager.get_plugin()

        #扫描特定路径下的extensions
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
        ...

        #定义的局部方法
        def _map_resource(collection, resource, params, parent=None):
            ...
            controller = base.create_resource(
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=allow_pagination,
                allow_sorting=allow_sorting)
            ...
            # 将这些resource加进router中
            return mapper.collection(collection, resource, **mapper_kwargs)


        # 遍历 {'network': 'networks', 'subnet': 'subnets','port': 'ports'}
        # 添加controller
        for resource in RESOURCES:
            _map_resource(RESOURCES[resource], resource,
                        attributes.RESOURCE_ATTRIBUTE_MAP.get(
                            RESOURCES[resource], dict()))

        for resource in SUB_RESOURCES:
            ...
            #其实操作和上面一个差不多,

由这个可以看出,添加的controller类型主要分为三类:(其实只要你在neutron目录下grep一下,看哪里调用了create_resource方法即可)

  1. OVSNeutronPluginV2
  2. extensions/*.py
  3. plugins/*.py

针对前两途径加载resource的类,下面慢慢进行描述。至于第三种,则是在各个不同的plugin内部额外实现的,不是必须的。

顺便简单的提一下,在neutron/api/extensions.py下的get_instance方法,这里其实也是和nova一样,是遍历目录下的py文件,来增加extension的

...
@classmethod
def get_instance(cls):
    if cls._instance is None:
        cls._instance = cls(get_extensions_path(),
                            NeutronManager.get_service_plugins())
...

Resource:OVSNeutronPluginV2的实现

看了代码的你肯定知道,OVSNeutronPluginV2这个类,作为core_plugin继承了好多的的类

class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                         external_net_db.External_net_db_mixin,
                         extraroute_db.ExtraRoute_db_mixin,
                         l3_gwmode_db.L3_NAT_db_mixin,
                         sg_db_rpc.SecurityGroupServerRpcMixin,
                         l3_agentschedulers_db.L3AgentSchedulerDbMixin,
                         agentschedulers_db.DhcpAgentSchedulerDbMixin,
                         portbindings_db.PortBindingMixin,
                         extradhcpopt_db.ExtraDhcpOptMixin,
                         addr_pair_db.AllowedAddressPairsMixin):

OVSNeutronPluginV2基本上没有什么自己的method,全靠它的”爹们”了。

随便抓两个来看下,比如NeutronDbPluginV2,他的method有get_port,create_network之类的,还有L3_NAT_db_mixincreate_router等。反正与db的操作,OVSNeutronPluginV2是不会管的,都在它的父类那边处理。

再看看OVSNeutronPluginV2继承的这些父类们:

#NeutronDbPluginV2继承自NeutronPluginBaseV2
class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                       CommonDbMixin):


class NeutronPluginBaseV2(...) :
   @abstractmethod
   def create_subnet(self, context, subnet):
   @abstractmethod
   def update_subnet(self, context, id, subnet):
   @abstractmethod
   def get_subnet(self, context, id, fields=None):
   @abstractmethod
   def get_subnets(self, context, filters=None, fields=None,

其类图如下:(仅展示部分)

Neutron的OVS-Plugin类图, 仅展示部分

基本上可以说有一个接口类(如图中的NeutronPluginBaseV2),定义了抽象方法,然后一个具体的db类来实现(如NeutronDbPluginV2,这里是采用SQLAlchemy来完成db模型的)

Resource:Extensions目录下resource的实现

/etc/neutron/api-paste.ini还有这么一项配置

[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

plugin_aware_extension_middleware_factory会调用到ExtensionMiddleware.__init__方法,其代码如下:

class ExtensionMiddleware(wsgi.Middleware):

    def __init__(self, application,
                 ext_mgr=None):
        self.ext_mgr = ...
        mapper = routes.Mapper()

        # ext_mgr.get_resources()其实在内部会调用每个extensions目录下的extension类的get_resource方法
        for resource in self.ext_mgr.get_resources():
            ...
            # 针对每个extension
            mapper.resource(resource.collection, resource.collection,
                            controller=resource.controller,
                            member=resource.member_actions,
                            parent_resource=resource.parent,
                            path_prefix=path_prefix)
            ...

比如在extensions下的securitygroup.py中的get_resources方法,看这个代码就知道其中可以处理security_groupsecurity_group_rule两类请求了。

class Securitygroup(extensions.ExtensionDescriptor):
    def get_resources(cls):
        ...
        exts = []
        plugin = manager.NeutronManager.get_plugin()
        for resource_name in ['security_group', 'security_group_rule']:
            ...
            controller = base.create_resource(collection_name,
                                              resource_name,
                                              plugin, params, allow_bulk=True,
                                              allow_pagination=True,
                                              allow_sorting=True)
            ex = extensions.ResourceExtension(collection_name,
                                              controller,
                                              attr_map=params)
            exts.append(ex)
        return exts

如此,Neutron-Server就已经基本上启动了,无外乎就是加载配置,router各种resource,然后就等待请求了。其中router哪些resource完全是由配置文件来决定的。
当然,在启动的过程中也会初始化db,这也就是为何在安装neutron的时候无需像novaglance等要执行db sync的原因了。

最后说一下,上面将各种操作都给map到位了,但是调用的时候呢?
Ok, 其实对资源的处理最终都会交由neutron/api/v2/base.py中定义的Controller类来处理,比如create-network请求,在create方法的末端,你会看到:

class Controller():
def create(...):
    ...
    obj_creator = getattr(self._plugin, action)
    if self._collection in body:
        # Emulate atomic bulk behavior
        objs = self._emulate_bulk_create(obj_creator, request,
                                            body, parent_id)
        return notify({self._collection: objs})
    else:
        kwargs.update({self._resource: body})
        # 在这里,最后就调用到了不同的plugins对不同的resource的处理
        obj = obj_creator(request.context, **kwargs)
        return notify({self._resource: self._view(request.context,
                                                    obj)})

通过obj_creator就可以调用到具体的handler了

关于Neutron的几篇不错的博客

懒得多写了, 就先后引用一下四篇文章吧,可以很清楚的描述一下,Neutron的各个概念。

1.http://www.ustack.com/blog/neutron_intro/

从Dashboard的的角度来讲解各个网络节点的连接关系,也有比如“网关臂”等的概念

2.http://docs.openstack.org/havana/config-reference/content/section_networking-scenarios.html

从三个业务场景的角度,讲解了为什么要这样安装配置,比如配置这个网桥的目的是什么等

3.http://openstack.redhat.com/Networking_in_too_much_detail

以一个demo的角度来讲解网络流图以及简单介绍了如router iptables和ovs的flow table的区别。

4.http://developer.rackspace.com/blog/software-defined-networks-in-the-havana-release-of-openstack-part-2.html

简单讲解了OVS的Flow Table

就这样,自己多动动手再加上这些应该可以很轻松的构建一个Neutron的网络流图的概念。后续如碰到好的文章再加进来

Chrome新标签页的定制

Chrome的小白用户太多了,很多人都不知道可以直接在地址栏输入内容就可以搜索,这个“租值消耗”太大了。就像输入验证码真是太耗时间了,然后就有人打起了这个主意一样。Google估计是看不下去了,就rape(qiang jian)了广大人民群众的新标签页(New Tab),然后就有了各种吐嘈, 这丫就长这样:
坑人的Chrome New Tab

为了干掉它,我可是纠结了不少功夫,走了不少弯路。

其实呢,在Chrome33+以前的版本中,还有这么个东西,在浏览器中输入下面的url:

chrome://flags/#enable-instant-extended-api

将其值设置为Disable了,然后重启下chrome就可以了。

不过想不通的是,在Chrome33+以后,竟然没有了这个选项……不得不说,Google,你好坑—-

还好,办法总是有的,于是在这种莫名奇妙的需求下就产生了两个插件:Replace Tab PageNew Tab Redirect。使用起来就非常简单了,而且更方便。这下可以回到原来清爽快捷的chrome://apps了(我的就是这个)。

easy吧~

Textual UML

闲扯

闲扯,就是扯下为啥我要写这篇博客。

code reading是门技术活,比必须理解别人的”语无伦次”,别人的”方言”,别人的”头脑发热”。好的程序员就像好的作家一样,那代码看起来,如行云流水,完全没有那种骂娘的念头,因为你看了觉得他有脑子。(这里顺便mark一下,中科院的COS,国耻啊,切记)。

但不管是好的代码,坏的代码,总有你需要整理的地方,整理一下便于理清思路。画流程图,是个很好的手段,只是可惜了,制作那些看着很舒服的类图,时序图是多么的繁琐。你要我用鼠标一个一个去拖那些UML还不如要了我的命呢。

继续坚持我的原则:可以懒,但必须懒得有个性。

作为一个合格的程序员来看,全手工的去画图是不合理的。懒得花时间去折腾那玩意,画图,呵呵,代码来画岂不是最合适不过的。


顺便说下TEX的诞生:
TEX的诞生,就是为了解决书籍排版的无奈的。想想,当你要更改其中一个页码,然后要一个一个地去修改页码,调整布局,加引用,调整公式图表等等,这个会让人疯的。。应该有不少人在完成各种论文的时候,都是采用这种蛮力法。Tex的诞生可谓提高了一部分生产力,其区别就像在用牛耕地的原始社会中突然出现了一辆现代化的农用机车。(不过,学习这种机车可不是那么一般人能够简单摸索出来的,Tex的难度更甚于此)

也就是说,要找一个DSL的语言来生成UML图吗(顺便扯下,UML是不是DSL呢)?

找了半天,有这么个网站可以比较好的介绍当前有那些UML工具,textual版本的UML工具,当然他也有很多其他的图形化工具。(网站有段时间打不开,翻了墙也打不开,为了确保在你看到这个文章的时候也可以用)

各种Textual UML工具

yUml

由yuml.me提供,免费的http接口可以使用,结合yuml命令行工具,可以很快的在vim下面编写完。语法最为简单,功能倒也不弱。学习曲线相当的低呀,语法直接看官网的几个demo就搞定了。

优点:
1.学习曲线低
2.基本功能都有
3.图形话做得不错
4.支持多种格式导出
缺点:
1.免费的只提供线上接口,商业版收费
2.功能太少了点,图片不支持自定义
3.不能识别现有代码,需要自己重新编写DSL。只能小范围的用用

Demo:

[Customer]<>-orders*>[Order]
[Order]++-0..*>[LineItem]
[Order]-[note:Aggregate root.]

yuml

UML Graph

依赖Graphviz,开源力作,不过,是java的阵营。。如果你懂java,想生成个简单的图图框框之类的,速度也是很快的。

优点:
1.学习曲线也低,如果你懂java的话
2.功能比较强大
3.图形话很简洁,不过很geek,一个程序猿要个那么好的图干啥(不是自嘲)
4.支持多种格式导出
缺点:
1.只支持java。同时若要完美的展示,必须现有的javadoc符合一定的格式才行
2.编写起来太麻烦了,需要从一开始就书写符合规范的javadoc才行

Demo:

/*
* Schema model
* UML User Guide p. 112
*/

/**
* @opt operations
* @opt attributes
* @opt types
* @hidden
*/
class UMLOptions {}

/* Define some types we use */
/** @hidden */
class Name {}
/** @hidden */
class Number {}

/**
* @has 1..* Member * Student
* @composed 1..* Has 1..* Department
*/
class School {
        Name name;
        String address;
        Number phone;
        void addStudent() {}
        void removeStudent() {}
        void getStudent() {}
        void getAllStudents() {}
        void addDepartment() {}
        void removeDepartment() {}
        void getDepartment() {}
        void getAllDepartments() {}
}

/**
* @has 1..* AssignedTo 1..* Instructor
* @assoc 1..* - 1..* Course
* @assoc 0..* - "0..1 chairperson" Instructor
*/
class Department {
        Name name;
        void addInstructor() {}
        void removeInstructor() {}
        void getInstructor() {}
        void getAllInstructors() {}
}

/**
* @assoc * Attends * Course
*/
class Student {
        Name name;
        Number studentID;
}

class Course {
        Name name;
        Number courseID;
}

/**
* @assoc 1..* Teaches * Course
*/
class Instructor {
        Name name;
}

uml graph

TextUML Toolkit

与其他的相比,这个倒还提供一个IDE(基于Eclipse),

优点:
1.学习曲线也低,语法长得像java
2.功能比较强大
3.图片基本可以实时的显示,IDE整合得不错
4.支持多种格式导出
缺点:
1.长得像java,但它不是java。。需要自己从头编写
2.IDE啊,太重量级了吧,尽管你还是跨平台的

package payment;

class PaymentMethod
end;

class Cheque specializes PaymentMethod
end;

class Paypal specializes PaymentMethod
end;

class CreditCard specializes PaymentMethod
end;

class Visa specializes CreditCard
end;

class AmericanExpress specializes CreditCard
end;

class Diners specializes CreditCard
end;

end.

TextUML Toolkit

MetaUML

几年没更新了,再加上语法也有点太冗余了,这个与现有的主流语言太迥异了,而且

Class.A("Point")
    ("+x: int",
        "+y: int") ();

Class.B("Circle")
    ("radius: int")
    ("+getRadius(): int",
        "+setRadius(r: int):void");

topToBottom(45)(A, B);

drawObjects(A, B);

clink(aggregationUni)(A, B)

metauml

MODSL

哇,超级干练的语法,你看看就知道了,用这个来整理下代码是奇快无比的。直接将原来的代码copy过来稍作修改就可以了。看看下面的demo就知道了,相当简练。当然,其功能并不强大,只能称得上一个简单的。而且,好几点没有更新了,功能也一直维持在几个基本的功能。

优点:
1.简练,上手容易。
2.可以作为一个Eclipse插件存在的,操作配置比较简单
缺点:
1.只支持类图和序列图
2.生成的图片
3.由内容编译生成图片略微麻烦了一点。没有直接提供原生的工具

collaboration diagram Sample {
    Main->Lexer.tokenize();
    Main->Parser.parse();
    Main->GraphLayout.apply();
    GraphLayout->SugiyamaLayout.apply();
    GraphLayout->NodeLabelLayout.apply();
    GraphLayout->EdgeLabelLayout.apply();
    Main->RenderVisitor.apply(graph);
    RenderVisitor->Graph.visit();
}

modsl

PlantUML

功能极其强大,底层基于java。下载了个plant-uml的jar包之后,只用执行

java -jar plantuml.jar your_plantuml_file

就会生成对应的png文件,速度很快的

优点:
1.功能真的很强大,基本上只有这一个支持像if-else这样的判断分支
2.使用起来也很方便,直接一个jar包就可以搞定了
3.图片的样式看起来听不错的
4.资源支持相当丰富,不管是插件,还是文档还是demo都很多
缺点:
1.语法看着有点纠结,真心是独立的DSL
2.需要安装java的

@startuml
(*)  --> "check input"
If "input is verbose" then
--> [Yes] "turn on verbosity"
--> "run command"
else
--> "run command"
Endif
-->(*)
@enduml

总结

总体感觉,还是plantuml比较好,支持的功能特性最多,非常有助于使用其来好好的整理下代码的结构与流程,很接近原生的思维导图,所以个人感觉也可以用来制作思维导图的工具,文本式的生成,比鼠标拖来拖去效率高太多了。
当然,如果有一个特定的工具能够将特定的代码转换成需要的DSL语言就非常美妙了。

/etc/resolv.conf中search和domain的作用

对这个一开始是一种半解的,Google打不开,顺手百度了半天都找不出一个合理的解释。。无语中。

“nameserver”指定要进行域名解析的dnsserver的IP地址。可以定义多个IP地址,按照顺序来请求

“domain”指定本地的domain,如果查询时的域名没有包含”.”,则会在此后面加上domain的值来进行解析

“search”若搜索的域名没有找到,则将域名拼接上search来搜索。下面会有例子来说明。

现实中有”qing.blog.sina.com.cn”,下面就以这个来说明,主要是说明”domain”和”search”的意义。

/etc/resolv.conf配置如下

nameserver 192.168.1.1
nameserver 8.8.8.8
domain  sina.com.cn
search  sina.com baidu.com


ping qing.blog.sina.com.cn   ### 这里就老老实实的走nameserver吧
ping blog
### 按顺序查找<strong>blog</strong>,<strong>blog.sina.com.cn</strong>,<strong>blog.sina.com</strong>和<strong>blog.baidu.com</strong>
### 这里的顺序是<strong>nameserver,domain,search</strong>

ping qing.blog
### 此处就只查找<strong>qing.blog</strong>,<strong>qing.blog.sina.com</strong>和<strong>qing.blog.baidu.com</strong>
### 这里的顺序是<strong>nameserver,<s>domain,</s>search</strong>
### domain此时没有起到作用,因为其定义是<strong>当搜索的domain没有"."时,则优先搜索domain,否则跳过</strong>

OK,这里基本上就可以说明/etc/resolv.conf的配置了,简单明了

写一篇博客,确实很花时间,这段时间比较忙,以后再抽空整理下学习OpenStack的心得。

清除Kindle Lirary

Mark it!!

网上搜了下,出来的基本上是都是js书签版本,而且支持IE。。。我就懒得折腾下那个IE了,直接开Chrome的开发工具台(右键审核元素,进入控制台即可)。再粘贴以下内。

当然和那个脚本一样,一次只能删一页,搞完再自己刷新吧。等有空再弄一个插件吧。思路是嵌入脚本利用localStorage来记录刷新次数。

jQuery('tr[asin]').each(function(i,e){
    console.log(jQuery(e).attr('asin'));
    jQuery.post('https://www.amazon.com/gp/digital/fiona/du/fiona-delete.html',
{'contentName':jQuery(e).attr('asin'),'loanId':'','sid':'你的sid','isAjax':1,'category':'kindle_pdoc','orderID':'undefined'});
})

相关资料

  1. Zhihu

Guacamole源码分析

环境的搭建

特别注明下版本信息和时间:Ubuntu 13.10@2013-12-03,已upgrade到最新state,gucamole-0.83

对于环境的搭建,可以参考我的另外一篇博客Ubuntu上搭建Guacamole,不过有点啰嗦..。此处可以再简单描述下。

Guacamole的安装

依赖的编译环境和测试运行环境,(不同的系统自己斟酌,假定你gcc等已经安装了,所以千万不要来一句wget找不到啊!- -)

apt-get install -y libfreerdp-dev libssl-dev libssh-dev libfreerdp-dev libvorbis-dev libpulse-dev libvncserver-dev libpango1.0-dev libcairo2-dev maven

apt-get install -y tomcat7 vnc4server

下载源码,0.83版本,路径均我放到/tmp下

cd /tmp
wget http://downloads.sourceforge.net/project/guacamole/current/source/guacamole-client-0.8.3.tar.gz && tar -xvf guacamole-client-0.8.3.tar.gz && cd guacamole-client-0.8.3/guacamole && mvn package && ln -sf /tmp/guacamole-client-0.8.3/guacamole/target/guacamole-0.8.3.war /var/lib/tomcat7/webapps/guacamole-0.8.3.war

wget http://jaist.dl.sourceforge.net/project/guacamole/current/source/guacamole-server-0.8.3.tar.gz && tar -xvf guacamole-server-0.8.3.tar.gz && cd guacamole-server-0.8.3 && ./configure && sed -i 's/-pedantic//' src/protocols/ssh/Makefile && make && make install && guacd

Guacamole配置

配置guacamole,参见http://guac-dev.org/doc/gug/configuring-guacamole.html,没有在环境变量中定义`GUACAMOLE_HOME`,默认路径在`/usr/share/tomcat7/.guacamole/`,配置文件参见我的另外一篇博客[Ubuntu上搭建Guacamole](http://www.lnmpy.com/install-guacamole/)

Guacamole的MySQL扩展

整合MySQL的登录验证的模块参见http://guac-dev.org/doc/gug/mysql-auth.html或者[Ubuntu上搭建Guacamole](http://www.lnmpy.com/install-guacamole/)中有描述


Guacamole的源码分析

guacamole结构上分为4层,建议先阅读下http://guac-dev.org/doc/gug/guacamole-architecture.html和http://guac-dev.org/doc/gug/guacamole-protocol.html,就可以对Guacamole的架构和协议有个基本的认识。

>

  1. JS (WebSocket/xmlhttprequest + canvas)
  2. JavaServlet
  3. guacd, 底层的daemon
  4. libfreerdp,libssh等

Guacamole协议

原文其实也说不大清,但其实通过修改下代码,JS与JavaServlet, Servlet与guacd交互,都是采用这种格式。我贴下从guacd中抓下来的部分日志,与原文一致。

4.sync,13.1386052271656;0,1.0,1.7,2.16;l,2.14,1.0,1.0,1.0,1.0,3.255;4.rect,1.0,3.160,3.285,2.10,2.19;5.cfill,2.14,1.0,3.153,3.153,3.153,3.255;4.rect,2.-1,1.0,1.0,3.520,2.19;5.cfill,1.6,2.-1,3.103,3.255,3.103,3.255;4.rect,2.-2,1.0,1.0,3.520,2.19;5.cfill,2.14,2.-2,1.0,1.0,1.0,3.255;4.copy,2.-1,1.0,1.0,3.520,2.19,2.14,2.-2,1.0,1.0;4.copy,2.-2,2.10,1.0,2.10,2.19,2.14,1.0,1.0,3.285;4.copy,2.-2,2.20,1.0,2.10,2.19,2.14,1.0,2.10,3.285;4.rect,2.-1,3.520,1.0,2.10,2.19;5.cfill,1.2,2.-1,1.0,1.0,1.0,3.255;3.png,2.14,2.-1,3.520,1.0,380.iVBORw0KGgoAAAANSUhEUgAAAAoAAAATCAYAAACp65zuAAAABmJLR0QA/wD/AP+gvaeTAAAA0ElEQVQokeXQO0oDURSA4e/eHVjaiJWgWNiI67AZV5BnZekqLBMmVboMBMQXgjYuwS3YpkmC+IqEuTZJHMkKxL89H+fA4T8WWqm1USpHOMtDfl4dNlLjCc95yI9jN3QneEBWRbVU28EBCogQhAGO6qm+tYRRzPCWpJsVjOIVPoJQ3XqC617ova9gJ3Reg3C7hO3U3sU+Bj8XFiWpSNJhMzW35+YZJmPj+zU4M7vDC7LF5othGH6twX7of+IySafYK5VF9QvR7wbYxGhq+uiP9Q20D0RUZK/+VAAAAABJRU5ErkJggg==;

当然,guacd与libfreerdp和libfreerdp与rdp-server是怎么交互的,这个就需要咱自己来整理了。

JS (WebSocket/xmlhttprequest + canvas)

核心就是那几个js文件:

scripts文件夹下的:service.js,admin-ui.js等

guacamole-common-js下的guacamole.js,layer.js,tunnel.js等。

script

*-ui.js: 如其名,只是用来处理dom-ui的

session.js: 使用localStorage来存取的,保存的是诸如clipboard,其提供了钩子函数

//钩子函数,外部赋值,又reload调用
this.onchange = null;

/*
 * 监听storage,也就是说注册的外部函数会被自动调用。
 * 典型的的是你在rdp里面粘贴板内容会更新到在textarea[id="clipborad"]中
 */
window.addEventListener("storage", guac_state.reload, false);

history.js: 同session.js,只是处理的是你的connection记录。

service.js: 提供了GuacamoleService核心类,其定义了GuacamoleService.Protocol,GuacamoleService.Protocol.Parameter,GuacamoleService.Connection,GuacamoleService.ConnectionGroup等一些属性。
还提供了以下方法(只展示部分)

/*
 * 有权限的用户能够操作自己的Connection
 */
GuacamoleService.Connections = {
    'list':*,
    'create':*,
    'move':*,
    ...
}
/*
 * admin用户可以查看其他用户,会使用到这些方法
 */
GuacamoleService.Users = {
    'list':*,
    'create':*,
    ...
}
/*
 * 创建Connection的过程中会提示选择Protocol,
 */
GuacamoleService.Protocols = {
    'list':*
}

guacamole-common-js

mouse.js,keyboard.js,oskeyboard.js: 创建实例会注册了一堆像mouseout之类的监听行为,其会被scripts/client-ui.js调用。

/*
 * 以下代码在scripts/client-ui.js中
 */
var keyboard = new Guacamole.Keyboard(document);
...
// 定义好钩子函数,会帮我们自动调用
keyboard.onkeyup = function (keysym) {
   guac.sendKeyEvent(1, keysym);
   ...
/*
 * 以下代码在guacamole-common-js/guacamole.js中
 */
this.sendKeyEvent = function(pressed, keysym) {
  if (!isConnected())
    return;
  tunnel.sendMessage("key", keysym, pressed);// 利用tunnel.js来发送请求
};

//!!!!注意,尽管又是监听事件,又是请求网络,但这里并不涉及到图形的绘制,其不与layer.js直接交互

layer.js: 提供canvas, 以及围绕这这个canvas的各个自定义的接口

// 创建一个canvas, 然后各种在这个canvas上进行操作
var display = document.createElement("canvas");

audtio.js: 没啥特别的, 只是需要提出的是:

if (window.webkitAudioContext) {
    // 使用webkitAudioContext来播放,当然性能更高了
}else{
    // 使用Audio类,使用base64编码的值来播放
}

tunnel.js: 判断浏览器是否支持WebSocket,如果不支持则采用性能较低的xmlHtttpRequest,轮询。这里只是网络请求的封装,并不涉及到协议格式。这里我也没有太多细看。懵懂,点到为止。

guacamole.js: 最核心的部分了,其主要涉及两项任务:(业务实现较多,但也就这样了)

  • JS部分协议的定义,解析数据,重绘layer,如”size”操作等
  • 内部定义一个Interval,相当于while(true)来重绘layer。

JavaServlet

TODO

guacd, 底层的daemon服务

分为三个部分:

  • guacd
  • libguac
  • protocols

guacd

guacd只是一个简单的daemon,只用来监听网络。

int main(...) {
    ...
    for (;;) {
        ...
        connected_socket_fd = accept(socket_fd, (struct sockaddr*) &client_addr, &client_addr_len); // BLOCK监听的
        fork(); // 判断那我就免了。
        socket = guac_socket_open(connected_socket_fd);
        guacd_handle_connection(socket);
        ...
    }
    ...
}

void guacd_handle_connection(guac_socket* socket) {
    ...
    // 先获取各种参数,libguac下的api
    select = guac_instruction_expect(socket, GUACD_USEC_TIMEOUT, "select");
    ...
    // 启动线程来执行网络操作
    guacd_client_start(client); //
    ...

}

int guacd_client_start(guac_client* client) {
    ...
    // 起了两个线程
    // 注意两个线程的参数都是client,也就是读写同一个socket,只是划分了责任而已
    pthread_create(&output_thread, NULL, __guacd_client_output_thread, (void*) client);
    pthread_create(&input_thread, NULL, __guacd_client_input_thread, (void*) client);
    ...
}

void* __guacd_client_input_thread(void* data) {
    // 当然有个while True
    ...
    // 读取指令,libguac下的api
    guac_instruction* instruction = guac_instruction_read(socket, GUACD_USEC_TIMEOUT);
    ...
    // 再调用libguac中的函数来实现对应指令的操作,之后再讨论细节
    guac_client_handle_instruction(client, instruction);
    ...
}

void* __guacd_client_output_thread(void* data) {
    // 当然有个while True
    ...
    // 发送同步信息
    guac_protocol_send_sync(socket, client->last_sent_timestamp)
    guac_socket_flush(socket);
    ...
    // 同步信息加上instruction指令信息,由protocol部分来实现。
    guac_protocol_send_sync(socket, client->last_sent_timestamp)
    client->handle_messages(client);
    guac_socket_flush(socket);
    ...
}

以上基本上就是guacd的功能结构了,结构简单,很清晰,我就一锅端了,不具体表明在哪个文件中

libguac

这里面就重点了解几个文件就行了,我挑几个描述下其部分代码。

socket.h/c

提供了guac_socket定义,一些socket操作方法

struct guac_socket {
    void* data;
    ...
    guac_socket_write_handler* write_handler; //需要外部定义的接口
    ...
}

ssize_t guac_socket_write(guac_socket* socket,
        const void* buf, size_t count) {
    ...
    int written = __guac_socket_write(socket, buffer, count);
    ...
}

static ssize_t __guac_socket_write(guac_socket* socket,
    const void* buf, size_t count) {
    ...
    if (socket->write_handler)
        return socket->write_handler(socket, buf, count); // 这个write_handler的初始化在socket-fd.c中
    ...
}
protocol.h/c

提供协议的各种操作指令的发送,以及定义了

typedef enum guac_composite_mode;
typedef enum guac_transfer_function;

// 一堆协议方法,这些是对外直接开放的,用户可以利用这些来编写自定义的protocol插件
int guac_protocol_send_move(guac_socket* socket, const guac_layer* layer,
    const guac_layer* parent, int x, int y, int z);


// 简单的sync可以看到实现如下,也就是对socket的写操作,只是封装了下,这样我们就接触不到任何内部的协议了
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp) {
    guac_socket_instruction_begin(socket); // 加锁
    ret_val =
           guac_socket_write_string(socket, "4.sync,")
        || __guac_socket_write_length_int(socket, timestamp)
        || guac_socket_write_string(socket, ";");

    guac_socket_instruction_end(socket); // 释放锁
    return ret_val;
}
instruction.h/c

提供instruction的定义

typedef struct guac_instruction {
    char* opcode;
    int argc;
    char** argv;
} guac_instruction;

guac_instruction* guac_instruction_read(guac_socket* socket,
        int usec_timeout) {
    while (...) {
        char c = socket->__instructionbuf[i++];
        if (c >= '0' && c <= '9')
            ...
        else if (c == '.') {
            if (...) {
                if (terminator == ';') {
                    ...
                    parsed_instruction->opcode = strdup(socket->__instructionbuf_elementv[0]);
                    memmove(socket->__instructionbuf, socket->__instructionbuf + i, socket->__instructionbuf_used_length - i);
                    socket->__instructionbuf_used_length -= i;
                    socket->__instructionbuf_parse_start = 0;
                    socket->__instructionbuf_elementc = 0;
                    return parsed_instruction;
                }
                else if (terminator != ',') {
                    return NULL;
                }

            }
        }
        else {
            ...
        }
    }
}

// 还记得guacd里面的这个方法的调用吧,其实也就是对协议交互流程的一个不成文的规定而已
guac_instruction* guac_instruction_expect(guac_socket* socket, int usec_timeout,
        const char* opcode) {
    instruction = guac_instruction_read(socket, usec_timeout);
    if (strcmp(instruction->opcode, opcode) != 0) {
        return NULL;
    }
    return instruction;
}

protocols(以rdp为例)

中文搜一下,竟然大多数的文章都是说rdp是微软的。。。好吧,我开始也被迷惑了,后来仔细搜了下,它原来是由国际电信联盟定义的,后来产生了各个不同的实现版本,基本都兼容。所以这个freerdp也是兼容windows的。

protocols/rdp下,代码不少,但其实更多的是对libfreerdp的封装,内部调用的是libguac的众多接口,并使用libguac下的protocol_*系列方法,。

// 协议需要实现guac_client_init
int guac_client_init(guac_client* client, int argc, char** argv){
    ...
    client->data = guac_client_data;// 操作client->data,即可实现数据的导出
    ...
    rdp_inst = freerdp_new();
    // 这几个相当于初始化rdp_inst,按照接口的要求定义一些钩子函数吧
    rdp_inst->PreConnect = rdp_freerdp_pre_connect;
    rdp_inst->PostConnect = rdp_freerdp_post_connect;
    rdp_inst->Authenticate = rdp_freerdp_authenticate;
    rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate;
    rdp_inst->ReceiveChannelData = __guac_receive_channel_data;
    //中间要设置一堆一堆的参数
    ...
    freerdp_connect(rdp_inst);
    ...
}

/*
 * 下边这个函数,就是在钩子函数rdp_freerdp_pre_connect中初始化相关的数据,
 * 定义的另外一钩子函数。
 * 反正我们就不管它的调用了吧,只负责实现好相关的接口,处理好连个协议之间的转换工作即可
 * 从下面的部分代码中就可以看到,rdp相关的操作最后都转成相应的guac_protocol调用了
 */
void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) {
    ...
    guac_client* client = ((rdp_freerdp_context*) context)->client;
    rdp_guac_client_data* data = (rdp_guac_client_data*) client->data;
    ...
    switch (patblt->bRop) {
        case 0x00:
            guac_protocol_send_rect(client->socket, current_layer, x, y, w, h);
            guac_protocol_send_cfill(client->socket,
                    GUAC_COMP_OVER, current_layer,
                    0x00, 0x00, 0x00, 0xFF);
            break;
        case 0xAA:
            break;
        case 0xCC:
        case 0xF0:
            ...
        case 0xFF:
            guac_protocol_send_rect(client->socket, current_layer, x, y, w, h);
            guac_protocol_send_cfill(client->socket,
                    GUAC_COMP_OVER, current_layer,
                    0xFF, 0xFF, 0xFF, 0xFF);
            break;
        default:
            ...
    }
}

guacd, libfreerdp,libssh等

好了,到这里我感觉就开始是天坑的开始了,rdp我愣是没找到一个完整讲解其协议格式的文件。https://github.com/FreeRDP/FreeRDP/wiki/Reference-Documentation,在这个上面列举了一堆内部使用或者引用到的技术。
不过蹦到MS的MSDN上,说找下Example看下的,看到的只有这个。。

00000000    03 00 00 00 10 00 00 00-00 00 00 00 01 00 00 00
................

03 00 00 00   MILCTRLCMD_OPENCONNECTION::controlCode = 0x00000003
10 00 00 00   MILCTRLCMD_OPENCONNECTION::messageSize = 0x10 = 16 bytes
00 00 00 00   MILCTRLCMD_OPENCONNECTION::unused (4 bytes)
01 00 00 00   MILCTRLCMD_OPENCONNECTION::connectingFlags = MilConnection::IsDwm

好吧,也确实是Example,不过,天坑,你就放过我吧。

好了,折腾了这么将近一天时间,也是搞得最长的一篇博客了,欢迎拍砖。

相关资料

  1. Guacamole
  2. FreeRDP
  3. RDP-GitHub-Refs
  4. Wikipedia
  5. MSDN

Ubuntu上搭建Guacamole

Leader提出让我自己去了解一下Guacamole这个开源项目,自己一看,以前也没有接触过VNC之类的东西,个人对于这种开源的项目并没有太大感觉。 不过就像当初没有用过ssh之前,我一样觉得这个应该可以很快的上手。OK,进入正题,首先就是环境的搭建了。

Guacamole的原理

引用官方的原理图(或者说叫流程图)。

Guacamole结构图

VNC之类的原理咱就先跳过不管了,这里面先简单说下各个模块:

Web Browser:普通的用户,使用的HTML5与后台进行交互。

Guacamole:处理与用户的交互,将页面上的操作请求处理下,再直接与下层的guacd来交互。

guacd:封装了各种RDP协议的中间层,如VNC等。

从图中可以看到,这个架构使得底层的guacd基本不需要进行什么修改和扩展了,直接部署上去就完事了,管你前端Web页面要改得怎样花里花俏的。

为啥这样,guacd使用C++实现的,底层处理封装各种不同的RDP,性能肯定高些,而用Java来生成前端则是方便吧,用PHP、Python等也可以,反正Guacamoleguacd的交互是采用独家的guacamole协议。这种设计思想和X-11是多么的神似。

Guacamole的部署(此处我以Ubuntu-12.04为例)

VNCServer的安装启动

ubuntu上安装VNC server很简单:

sudo apt-get install vnc4server

安装完后要给当前用户设置密码,这个密码就是连接VNC时要用到的:
接着输入:

vncserver :1  # :1 表示显示号,启启用的端口为5901

:1代表display-number,这里我用Chrome的插件VNC Viewer来测试的。连上去当然只有一个ssh客户端了,配置下也可以连各种X-window,这个不是我们的重点。

注意vncserver默认的端口是5900,如果采用VNC客户端的话,直接使用display-number即可,涉及到具体端口的,则使用端口display-number+5900,从0开始。

Guacamole-Web的部署

接下来安装Java-Web端的环境,直接输入,就会自动把依赖的环境也配置好。

sudo apt-get install guacamole-tomcat
//会提示重启tomcat,确认即可

其就是安装好java,tomcat之后,发布guacamole的war包。同时在/etc/guacamole中可以看到两个配置文件:guacamole.propertiesuser-mapping.xml

guacamole.properties的配置如下:

// guacd服务绑定的ip和port,必须和guacd中配置相同
guacd-hostname: localhost
guacd-port:     4822

//这个不用管
auth-provider: net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider
basic-user-mapping: /etc/guacamole/user-mapping.xml

user-mapping.xml的配置如下:

<user-mapping>
    <authorize username="USERNAME" password="PASSWORD"> //在浏览器中的登录账号密码
        <protocol>vnc</protocol> //rdp类型
        <param name="hostname">localhost</param> //VNC的ip,可以是任意的ip和hostname,此处以本机为例
        <param name="port">5901</param> //这个和你的VNC端口类似,注意其对应于 :1
        <param name="password">password</param>
    </authorize>
</user-mapping>

这两个配置文件修改后,Guacamole会动态重新加载,只要你别改错了就行。

guacd的配置和部署

sudo apt-get install guacd
//其实直接sudo apt-get install guacamole可以同时安装好guacd和guacamole-tomcat。

注意默认的例子是连接localhost下的4822端口,需要的话自己再修改下源码即可。在guacd/daemon.c中可以看到代码中对ip-port的绑定。

char* listen_address = NULL; /* Default address of INADDR_ANY */
char* listen_port = "4822";  /* Default port */

Guacamole初体验

好了,直接在浏览器中打开http://localhost:8080/guacamole/就可以看到:

Guacamelo登录界面

登录呢,一个简单的Web Terminal:

Guacamelo登录效果图

这只是一个简单的安装流程吧,后续的集成再来总结吧

Proudly powered by Hexo and Theme by Hacker
© 2018 Elvis Macak