EPEL版 OpenStack folsomの設定(Horizon編)

Horizonのlocal_settings.pyでハマったのでメモ。

Horizonの設定ファイル

Horizonは、PythonのWebアプリケーションフレームワークであるdjangoの上に構成されており、EPEL版のパッケージでインストールした場合、設定ファイルは以下に置かれています。

  • /usr/share/openstack-dashboard/openstack_dashboard/local/local_settings.py

設定すべきパラメータ

設定すべきパラメータは以下の通り多くはありません。
※修正後はhttpdのrestartを忘れずに!

KEYSTONE_HOST = "Kenstoneが稼動しているホスト名またはIPアドレス"
OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % KEYSTONE_HOST
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Keystoneで作成した管理者ロール名"

OPENSTACK_KEYSTONE_DEFAULT_ROLE について

どうも、このパラメータを参照しているのは、Horizonの機能のなかでもUpdateProjectMembersActionクラスだけのようです。
このクラスは、テナント情報を参照したり、テナントを作成したりする際に利用されます。でもなぜかテナントを削除する際には呼ばれない...

/usr/lib/python2.6/site-packages/horizon/dashboards/syspanel/projects/workflows.py

OPENSTACK_KEYSTONE_DEFAULT_ROLEは、__init__()内で呼ばれているapi.get_default_role()が利用しているのみ(たぶん)です。もっといい方法あるんじゃないかなこれ...

class UpdateProjectMembersAction(workflows.Action):
    default_role = forms.CharField(required=False)

    def __init__(self, request, *args, **kwargs):
        super(UpdateProjectMembersAction, self).__init__(request,
                                                         *args,
                                                         **kwargs)
        err_msg = _('Unable to retrieve user list. Please try again later.')
        project_id = ''
        if 'project_id' in args[0]:
            project_id = args[0]['project_id']

        # Get the default role
        try:
            default_role = api.get_default_role(self.request).id    <== ココで呼ばれるget_default_role()が見ている
        except:
            exceptions.handle(self.request,
                              err_msg,
                              redirect=reverse(INDEX_URL))
        self.fields['default_role'].initial = default_role

        # Get list of available users
        all_users = []
        try:
            all_users = api.keystone.user_list(request)
        except:
            exceptions.handle(request, err_msg)
        users_list = [(user.id, user.name) for user in all_users]

        # Get list of roles
        role_list = []
        try:
            role_list = api.keystone.role_list(request)
        except:
            exceptions.handle(request,
                              err_msg,
                              redirect=reverse(INDEX_URL))
        for role in role_list:
            field_name = "role_" + role.id
            label = _(role.name)
            self.fields[field_name] = forms.MultipleChoiceField(required=False,
                                                                label=label)
            self.fields[field_name].choices = users_list
            self.fields[field_name].initial = []

        # Figure out users & roles
        if project_id:
            for user in all_users:
                try:
                    roles = api.roles_for_user(self.request,
                                               user.id,
                                               project_id)
                except:
                    exceptions.handle(request,
                                      err_msg,
                                      redirect=reverse(INDEX_URL))
                if roles:
                    primary_role = roles[0].id
                    self.fields["role_" + primary_role].initial.append(user.id)

    class Meta:
        name = _("Project Members")
        slug = "update_members"
/usr/lib/python2.6/site-packages/horizon/api/keystone.py

ここで、ユーザが持っているロールがOPENSTACK_KEYSTONE_DEFAULT_ROLEにマッチしているかどうかをチェックしています。
マッチしていない場合は...UpdateProjectMembersActionクラスインスタンスを生成する(つまりテナントの参照や作成をする)際に、default_role = api.get_default_role(self.request).id)部分で

AttributeError: 'NoneType' object has no attribute 'id'

となってテナントの参照及び作成機能が利用できません。
その際、Horizon上にはError: An error occurred. Please try again.というエラーメッセージが出力されます。
もちろん、OPENSTACK_KEYSTONE_DEFAULT_ROLEに適切なロール名を設定しない限り、try againしても無駄です:)
これ、なんかやっぱりイマイチな気がする...

def get_default_role(request):
    """
    Gets the default role object from Keystone and saves it as a global
    since this is configured in settings and should not change from request
    to request. Supports lookup by name or id.
    """
    global DEFAULT_ROLE
    default = getattr(settings, "OPENSTACK_KEYSTONE_DEFAULT_ROLE", None)
    if default and DEFAULT_ROLE is None:
        try:
            roles = keystoneclient(request, admin=True).roles.list()
        except:
            roles = []
            exceptions.handle(request)
        for role in roles:
            if role.id == default or role.name == default:  <== ココでチェックしている
                DEFAULT_ROLE = role
                break
    return DEFAULT_ROLE