2008年4月アーカイブ

昨日の朝停電があって、サーバ系がみんな落ちていた。
目覚ましの止まっていた時間からすると1分とか2分とからしいんだけど、一度落ちたサーバは再起動しない。
まあ、自動で再起動しないのは良いんだけど、HDDの書き込み中に落ちたりしてdiskが読めなくなってしまったりすると困るので、UPSの導入を検討する。
検討すると言っても、真面目に調べたわけではなくて、ヨドバシ.comのページを価格の安い順にソートして、適当に選ぶ。候補はオムロンとAPCの500VA/300Wの奴。
APCのページを見ると、真面目に容量を計算しろみたいなことが書いてある。
TeraStationが57Wで、サーバPCが・・・わからない。最大300Wくらいの電源だったと思うけど・・。
で、APCの方はportsにsysutils/apcupsdってのがあるので、APCのes500って奴にした。
後で調べたら、オムロンの方もFreeBSD用のソフトがフリーで存在した&TeraStation対応だった&安かったので、オムロンでも良かったかも。
しかし、どちらにしろUPSのUSB出力は一つなので、FreeBSDのサーバとTeraStationの両方にshutdownを送ることはできない。
apcupsdについて調べていたら、NIS(Network Information Serverだと。ypではない)とか言う機能があって、UPSと直接通信するマシンをサーバにして、電源供給を受けるマシンをクライアントにして連携プレイができるらしい。
TeraStationでapcupsdをclientモードで入れることができれば、うまくshutdownさせられそうだ。
landiskはそう言うことはできなさそうなので、バッテリーがなくなる前に停電が復旧しなければ今までと同じ。
と、言うことでUPSが届く前にapcupsdをインストール。
FreeBSDの方は、portsからsysutils/apcupsdをインストールするだけ。あとは、/usr/local/etc/apcupsd/apcupsd.confを修正する。
細かいところは後で調整が必要だと思うけど、変更点は以下のような感じ。
UPSCABLE usb
UPSTYPE usb
DEVICE
#NISIP 192.168.0.1
※ NISIPを限定しようとしたら、apcaccessコマンドとか、cgiとかが127.0.0.1にアクセスするので、デフォルトに戻した。アクセス制限はipfで。cgiはパラメータでIP指定ができる。
続いて、TeraStationの方。
FreeBSDの方をportsでインストールしたのでFreeBSDの/usr/ports/distfilesにapcupsdのソースがある。
こいつを、TeraStationにコピーして、
# ./configure --prefix=/opt/usr/local --exec-prefix=/opt/usr/local --enable-powerflute
とconfigureを実行してみたけど、どうもprefixとexec-prefixは効かないっぽい。
あと、mailがないと言う警告が出て、wallがないと言うエラーが出る。
警告は無視する。
TeraStationは人がログインして使うマシンではないので、wallは必要ないので、/usr/local/bin/にwallと言う名前のダミースクリプトを作成した。
さらに、--enable-powerfluteを入れると、ncursesのライブラリを要求されるので、
  • ncurses-5.4-0vl2.ppc.rpm
  • ncurses-devel-5.4-0vl2.ppc.rpm
  • gpm-1.20.1-35vl2.ppc.rpm
の3つのrpmを持ってきてインストールした。
で、再度configure。結果は以下のような感じ。(これを見る限り、prefixは効いていない)
  Host:                       powerpc-unknown-linux-gnu -- unknown unknown
  Apcupsd version:            3.14.2 (15 September 2007)
  Source code location:       .
  Install binaries:           /sbin
  Install config files:       /etc/apcupsd
  Install man files:          /usr/share/man
  Nologin file in:            /etc
  PID directory:              /var/run
  LOG dir (events, status)    /var/log
  LOCK dir (for serial port)  /var/lock
  Power Fail dir              /etc/apcupsd
  Compiler:                   g++ 3.3.6
  Compiler flags:             -g -O2 -Wall
  Linker:                     gcc
  Linker flags:               -g -O
  Host and version:           unknown unknown
  Shutdown Program:           /sbin/shutdown
  Port/Device:                /dev/ttyS0
  Network Info Port (CGI):    3551
  UPSTYPE                     apcsmart
  UPSCABLE                    smart

  drivers (no-* are disabled): apcsmart dumb net no-usb no-snmp pcnet no-test

  enable-nis:                 yes
  with-nisip:                 0.0.0.0
  enable-cgi:                 no
  with-cgi-bin:               /etc/apcupsd
  with-libwrap:
  enable-nls:                 no
  enable-libintl:             no
  enable-powerflute:          yes
  enable-pthreads:            yes
  enable-dist-install:        yes
  enable-gapcmon:             no
そしてmake。
make -n installすると、やっぱり/sbin/と/etc/apcupsdにインストールされるようだ。
そこで、
# DESTDIR=/opt/usr/local make install
でインストールして、必要なファイルのリンクをはった。
# ln -s /opt/usr/local/etc/apcupsd /etc/
# ln -s /opt/usr/local/sbin/apcupsd /sbin/
# ln -s /opt/usr/local/sbin/powerflute /sbin/
後は、/etc/apcupsd/apcupsd.confを修正。修正箇所は、
UPSCABLE ether
UPSTYPE net
DEVICE 192.168.0.1:3551
NETSERVER on

と言ったところでUPSが届いたので、早速つないでみた。
サーバPC、TeraStation、光終端装置をUPSにつなぎ、サーバとUPSをUSBで繋ぐ。
で、FreeBSDでapcupsdを起動。
apcaccessを実行してみると、なんかずらずらと表示される。
で、cgiをwebサーバ上の実行できるところにリンクして、multimon.cgiとかupsstats.cgiを実行してみる。
multimon.cgiは、hosts.confを見るようで、ここにIPアドレスを書いてあげたら、FreeBSDとTeraStationのapcupsdの監視ができるようになった。
試しにUPSのコンセントを抜いてみたら、FreeBSDの/var/log/apcupsd.eventsに電源が落ちたとのログが出て、UPSが結構な音量でピーピー言う。
で、TeraStationの方のログを見てみたら、Communications with UPS lost.とか出てる。
hubの電源が落ちたのが問題なようだ。
しょうがないので、光終端装置の方を諦めて、hubの電源をUPSに繋ぐ。

これで、一応バッテリによるサーバ電源のバックアップまではできるようになったはず。
後は、バッテリー残何%でshutdownさせるかとか、shutdown開始してから何分でバッテリーをoffにするかとかのパラメータを調整する必要があるけど、力つきたので今日はここまで。
さらに、snmpに対応していた気がするので、cactiでグラフ化できるようにしたい。
さらに、TeraStationのapcupsdを起動スクリプトに組み込まないとなあ。
と、言うわけでcactiでグラフ表示。
apcupsdのsnmp対応と言うのは、snmpを出すUPSの出力を取れると言うものらしく、シリアルやUSBで繋がったUPSの情報をapcupsdがsnmpで出してくれると言うものではないらしい。
久しぶりにcactiを触ると、グラフの追加の仕方とか覚えてなくて泣く。
ネットで調べた感じだと、net-snmpのexec機能を使うパターンが多いみたいだけど、今回は以前CPU(pentium-m)の周波数をグラフ化したときと同じ方法を使った。
  1. データ収集スクリプトを用意する。(とりあえず、電源電圧、バッテリー%、負荷%を取ってみた)
    /usr/local/share/cacti/scripts/apcups.pl
    #!/usr/bin/perl
    
    open(FILE, "/usr/local/sbin/apcaccess|");
    while() {
        if (/^LINEV/) {
            /([0-9\.]+)/;
            $linev = $1;
        }
        if (/^LOADPCT/) {
            /([0-9\.]+)/;
            $loadpct = $1;
        }
        if (/^BCHARGE/) {
            /([0-9\.]+)/;
            $bcharge = $1;
        }
    }
    close(FILE);
    
    print "linev:$linev loadpct:$loadpct bcharge:$bcharge";
    
  2. cactiのコンソールから、Data Input Methodを追加する
  3. cactiのコンソールから、Data Sourcesを追加する
  4. cactiのコンソールから、Graph Managementを追加する
これで、グラフができた。
自分のblogでamazonの商品の個別リンクを作成するのに、aws.plをずっと使っていたのだけれど、最近amazonのWSはECS4.0になって古いAPIは使えなくなってしまった。
で、まだまだ不完全なんだけど、自分の使い方では問題ないのでたぶんこれ以上手を入れないと思うので、現時点のものを公開しておく。
今の所、商品の個別リンクを貼るために、
<MTAws search="AsinSearch" query="Asinを,で並べる">
<div style="border: solid 1px #666666; padding: 1em;">
<a href="<$MTAwsurl$>"><img src="<$MTAwsImageUrlSmall$>" align="left">
『<$MTAwsProductName$>』<br/>
著者:<$MTAwsAuthors$><br/>
出版社:<$MTAwsManufacturer$><br/>
発行日:<$MTAwsReleaseDate$>
</a>
</div>
</MTAws>
みたいな使い方ならば問題ない。(blogの文字コードはutf8で確認)
上で出てこないタグについてはどうなるかわからない。
参考にしたのは、特集:前編 WebサービスをAmazonで知る――ECS 4.0でアフィリエイト
以前と違って、query=に並べた順番で結果を返してくれないので、無理やりAsinで結果をソートしている。
一応、以前書いたblogのエントリを書き換えたくなかったので、互換性を保つようにしてあるのだけれど、これ以上機能を追加するのであれば、最近のMTのpluginの書き方を勉強して、1から作り直した方が良さそうだ。
元のソースの著作権は平田さんにあります。利用条件等は元のものに従います。
aws.pl
# aws.pl
# (C) 2003 Daiji Hirata
#  All Right Reserved.

use MT::ConfigMgr;
use MT::Template::Context;
use LWP::UserAgent;
use XML::Simple;
use Jcode;
use strict;

my $VERSION = '1.0a-ecs4';

my %config;
$config{SubscriptionId} = 'あなたのSubscriptionId';
$config{associate_id} = 'あなたのアソシエイトID';

my @aws_tags = (
		# lite
		'url', 'ImageUrlSmall', 'ImageUrlMedium', 'ImageUrlLarge', 'ProductName', 'Asin', 
		'Manufacturer', 'Availability', 'ListPrice', 'OurPrice', 'UsedPrice', 'Catalog',
		# heavy
		'Media', 'Isbn', 'ReleaseDate', 'SalesRank', 'Upc', 'ThirdPartyNewPrice', 
);

my $elements = {
    'ItemLookup' => [ "SubscriptionId", "AssociateTag", "Operation", "ItemId", "ResponseGroup"],
};

MT::Template::Context->add_container_tag(Aws => \&aws);

MT::Template::Context->add_tag(AwsAddCart => \&aws_addcart);

MT::Template::Context->add_tag(AwsAuthors => \&aws_authors );
MT::Template::Context->add_tag(AwsArtists => \&aws_artists );
MT::Template::Context->add_tag(AwsTracks => \&aws_tracks );
MT::Template::Context->add_tag(AwsXML => \&aws_xml );
MT::Template::Context->add_tag(AwsRequestUrl => \&aws_requesturl );
MT::Template::Context->add_tag(AwsTotalPages => \&aws_totalpages );
MT::Template::Context->add_tag(AwsTotalResults => \&aws_totalresults );
MT::Template::Context->add_tag(AwsAvgCustomerRating => \&aws_avgcustomerrating );
MT::Template::Context->add_tag(AwsTotalCustomerReviews => \&aws_totalcustomerreviews );
MT::Template::Context->add_tag(AwsVersion => \&aws_version );

foreach my $tag (@aws_tags) {
    MT::Template::Context->add_tag('Aws'.$tag => sub { my $ctx = shift; aws_detail($ctx, $tag)});
}

sub aws {
    my ($ctx, $arg) = @_;
    my $builder = $ctx->stash('builder');
    my $tokens = $ctx->stash('tokens');
    my $res;
    my %q;

    use MT::ConfigMgr;
    my $cfg = MT::ConfigMgr->instance;
    my $charset = {'Shift_JIS'=>'sjis','ISO-2022-JP'=>'jis','EUC-JP'=> 'euc',
                   'UTF-8'=>'utf8'}->{$cfg->PublishCharset} || 'utf8';

    $q{SubscriptionId} = ($arg->{SubscriptionId}) ? $arg->{SubscriptionId} : $config{SubscriptionId};
    if (!($q{SubscriptionId})) { return $ctx->error("MTAWS needs your SubscriptionId: $q{SubscriptionId}."); }
    $q{AssociateTag} = ($arg->{associate_id}) ? $arg->{associate_id} : $config{associate_id}; 
    $q{ResponseGroup} = ($arg->{ResponseGroup}) ? $arg->{ResponseGroup} : "ItemAttributes,Images";
    if (!($q{AssociateTag})) { $q{AssociateTag} = 'dh0dc-22'; }
    #$q{type} = ($arg->{type}) ? $arg->{type} : 'lite';
    #$q{mode} = ($arg->{mode}) ? $arg->{mode} : '';
    #$q{page} = ($arg->{page}) ? $arg->{page} : 1;
    $q{lastn} = ($arg->{lastn}) ? $arg->{lastn} : -1;
    $q{offset} = ($arg->{offset}) ? $arg->{offset} : -1;
    #$q{locale} = ($arg->{locale}) ? $arg->{locale} : $config{locale};
    if ($arg->{search} && $arg->{query}) { 
	#$q{search} = $arg->{search}; 
	#$q{query} = ($charset ne 'utf8') ? Jcode->new($arg->{query}, $charset)->utf8 : $arg->{query};
	#$q{$arg->{search}} = utf2entity($q{query},1); 
	if ($arg->{search} eq 'AsinSearch') {
	    $q{Operation} = 'ItemLookup';
	    $q{ItemId} = $arg->{query};
	} else {
	    return $ctx->error("Unsupported method $arg->{search}");
	}
    } else {
	return $ctx->error("No search method and/or query");
    }
    my $url = 'http://ecs.amazonaws.jp/onca/xml?Service=AWSECommerceService&';

    my @query;

    foreach my $key (@{$elements->{$q{Operation}}}) {
        if (defined $q{$key}) { 
            push @query, "$key=$q{$key}";
        }
    }

    $url .= join "&amp;", @query;

    $ctx->stash('RequestUrl', $url );
    $ctx->stash('Associate_Id', $q{AssociateTag} );

    my $ua = new LWP::UserAgent;
    $ua->agent("MTAWS");
    my $http_request = new HTTP::Request('GET', $url);
    my $http_response = $ua->request($http_request);
    my $content = $http_response->{'_content'};

    my $tree;
    $tree = XMLin($content);
    if (!($tree)) { return $ctx->error("XML incorrect:\n"); }
    $ctx->stash('XML', $content );
    my @details;

    if (ref $tree->{Items}->{Item} eq 'ARRAY') {
	push @details, @{$tree->{Items}->{Item}};
    } else {
	push @details, $tree->{Items}->{Item};
    }

    #$ctx->stash("TotalResults", $tree->{TotalResults});
    #$ctx->stash("TotalPages", $tree->{TotalPages});

@details = sort {$a->{ItemAttributes}->{EAN} cmp $b->{ItemAttributes}->{EAN}} @details;
    for my $detail (@details) {
	next if (($q{offset}--) > 0);
        $ctx->stash("Detail", $detail);
        
        defined(my $out = $builder->build($ctx, $tokens))
	    or return $ctx->error($ctx->errstr);
        $res .= $out;
        if ($q{lastn}>0) { $q{lastn}--; }
        last if ($q{lastn} == 0);
    }
    return $res;
}

sub utf2entity {
    my ($str, $amazon) = @_;
    $str =~ s/([^\w,])/'%'.unpack("H2", $1)/ego;

    if ($amazon ) {
	$str =~ s/%/%25/g;
    }
    $str;
} 

sub aws_requesturl {
    my $ctx = shift;
    $ctx->stash("RequestUrl") || ''; 
}

sub aws_xml {
    my $ctx = shift;
    $ctx->stash("XML") || ''; 
}

sub aws_totalresults {
    my $ctx = shift;
    $ctx->stash("TotalResults") || ''; 
}

sub aws_totalpages {
    my $ctx = shift;
    $ctx->stash("TotalPages") || ''; 
}

sub aws_authors {
    my $ctx = shift;
    my $author;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    if (ref $detail->{ItemAttributes}->{Author} eq 'ARRAY') { 
        $author = join ", ", @{$detail->{ItemAttributes}->{Author}};
    } else {
        $author = $detail->{ItemAttributes}->{Author} || '';
    }
    return Jcode->new($author, 'utf8')->utf8;
    return $author;
}

sub aws_detail {
    my ($ctx, $e) = @_;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    my $t;
    if ($e eq 'url') {
	$t = $detail->{DetailPageURL};
    } elsif ($e eq 'ImageUrlSmall') {
	$t = $detail->{SmallImage}->{URL};
    } elsif ($e eq 'ImageUrlMedium') {
	$t = $detail->{MediumImage}->{URL};
    } elsif ($e eq 'ImageUrlLarge') {
	$t = $detail->{LargeImage}->{URL};
    } elsif ($e eq 'ProductName') {
	$t = $detail->{ItemAttributes}->{Title};
    } elsif ($e eq 'Asin') {
	$t = $detail->{ItemAttributes}->{EAN};
    } elsif ($e eq 'Manufacturer') {
	$t = $detail->{ItemAttributes}->{Manufacturer};
    } elsif ($e eq 'ReleaseDate') {
	$t = $detail->{ItemAttributes}->{PublicationDate};
    } else {
	# 上記以外は未対応(と言うか手抜き)
	return '';
    }
    if (ref $t eq 'ARRAY') { 
        return join ", ", @{$t};
    } else {
        return $t || '';
    }
}

# 未対応
sub aws_avgcustomerrating {
    my $ctx = shift;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    $detail->{Review}->{AvgCustomerRating} || '';
}

# 未対応
sub aws_totalcustomerreviews {
    my $ctx = shift;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    $detail->{Review}->{TotalCustomerReviews} || '';
}

sub aws_artists {
    my $ctx = shift;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    if (ref $detail->{ItemAttributes}->{Artist} eq 'ARRAY') { 
        return join ", ", @{$detail->{ItemAttributes}->{Artist}};
    } else {
        $detail->{ItemAttributes}->{Artist} || '';
    }
}

# これは未対応
sub aws_tracks {
    my $ctx = shift;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    if (ref $detail->{Tracks}->{Track} eq 'ARRAY') { 
        return join ", ", @{$detail->{Tracks}->{Track}};
    } else {
        $detail->{Tracks}->{Track} || '';
    }
}

# これは未対応
sub aws_addcart {
    my ($ctx, $args) = @_;
    
    my $res;
    defined (my $detail = $ctx->stash("Detail")) or return '';
    my $button = ($args->{label}) ? $args->{label} : "Buy";

    $res = sprintf( "<form method=\"POST\" action=\"http://www.amazon.co.jp/o/dt/assoc/handle-buy-box=%s\">\n", $detail->{Asin} );
    $res .= sprintf( "<input type=\"hidden\" name=\"asin.%s\" value=\"1\">\n", $detail->{Asin} );
    $res .= sprintf( "<input type=\"hidden\" name=\"tag-value\" value=\"%s\">\n", $ctx->stash("Associate_Id") );
    $res .= sprintf( "<input type=\"hidden\" name=\"tag_value\" value=\"%s\">\n", $ctx->stash("Associate_Id") );
    $res .= sprintf( "<input type=\"submit\" name=\"submit.add-to-cart\" value=\"%s\">\n", $button);
    $res .= "</form></span>\n";

    $res;
}

sub aws_version {
    $VERSION;
}

1;
__END__

=head1 NAME

aws.pl - Movable Type Plugin of Amazon Webservices

=head1 SYNOPUS

    <MTAws dev-t="developer-token" associate_id="dh0dc-22" search="KeywordSearch" mode="books-jp" query="weblog" locale="jp">
    <$MTAwsurl$>
    <$MTAwsAuthors$>
    <img src="<$MTAwsImageUrlSmall$>">
    <$MTAwsAddCart label="Buy from Amazon!"$>
    </MTAws>

=head1 LICENSE

Please see the file F<README> in the package.

=head1 AUTHOR

Daiji Hirata, dh@uva.jp

=cut
2008年4月
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      

このアーカイブについて

このページには、2008年4月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2008年3月です。

次のアーカイブは2008年6月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.1.1