데코레이터 함수는 다른 함수의 동작을 수정하거나 확장할 수 있는 함수입니다.
데코레이터 함수는 주로 다음과 같은 용도로 사용됩니다:
- 로깅, 메트릭 수집 등 추가 동작 수행
- 예외 처리
- 권한 확인
- 테스트 목적
OpenStack에서 데코레이터 함수는 주로 다음과 같이 사용됩니다:
1
11. Application architecture (nova)
• Daemon process (using nova-network)
– nova-api (controller node)
– nova-conductor (controller node)
– nova-scheduler (controller node)
– nova-compute (compute node)
– nova-network (compute node)
• Source directory
– api.py : 타 프로세스 모듈의 함수를 import 하여 호출
– rpcapi.py : 타 프로세스 모듈을 MQ를 통하여 호출 (publisher)
– manager.py : MQ 로 호출된 함수가 invoke 됨 (subscriber)
– driver.py : Abstract Class
• Communication way
– 서로 다른 프로젝트 : Rest API
– 같은 프로젝트내의 서로 다른 프로세스 : MQ
16. How to Contribute
• Making an account at launchpad.net
• Join the OpenStack developers mailing list & #openstack-dev IRC Channel
• Confirming to code review system information
• Agreeing to the CLA (contributors License Agreement)
• Writing Blueprints (Gerrit & blueprints.launchpad.net/nova)
• Getting the OpenStack code (git clone)
• Setting up gerrit environment
– git remote add gerrit
ssh://StephenAhn@review.openstack.org:29418/openstack/nova.git
• Making a git new branch (git branch)
• Pushing the your code (git push)
17. Private Cloud 상품 구성
• 기본 상품
– Compute
– Object Storage
– Portal
– Monitoring
– Metering & Billing
– Operations
– Load Balancing
– Security (Anti-DDos, IPS, Firewall, IDS, WebFirewall, etc)
• 확장 상품
– DNS, Queue, Database, Hadoop
– Content Delivery network (CDN), Shared File System (SFS)
– Virtual Private Cloud (VPC), Hybrid Cloud
18. Hybrid Cloud Architecture
AWS Direct Connect
(USA East Region)
(BGP / VLAN)
1Gbps Dual Line
Active/Active(Standby)
Internet
Private
DC
Public GW
Private GW
AWS
DC
AWS US RegionPrivate US Region
VM
Subnet B
IP : 10.22.2.0/24
VM
VM
Subnet A
IP : 10.22.1.0/24
VM
Private : 10.22.0.0/16
VM
Subnet A
IP : 10.0.1.0/24
VM
VPC
VM
Subnet B
IP : 10.0.2.0/24
VM
L3Switch
VM
EC2 / S3
IP : 10.123.123.xx
VM S3
VPC : 10.0.0.0/16
19. 프로젝트 시작은?
• 네트워크는 큰 그림을 그리고 시작
• 개발은 최소의 인력(pizza team)으로 필요한 기능부터
• Product roadmap 은 항상 현행화하여 공유
• 운영은 자동화로 인력을 최소화
• 소규모 프로젝트 단위로 개발자와 네트워크 엔지니어로 구성
• S/W, N/W, Architecture 를 모두 아는 인력이 적어도 한 명은 필요
• Product Manager == 의사결정권자
• 개발자는 다른 개발자의 소스도 이해할 수 있어야 함
20. 바꾸고 싶은 생각들
• Virtual Machine 은 Dedicated physical server 가 아니다.
– 언제든지 down 될 수 있다. 빨리 살리는 것이 중요
– 애플리케이션 아키텍처로 이중화
• Physical server network 이중화는 필요 없다.
– 규모의 경제
• 작은 규모는 Virtualization 으로 해결하는 것이 더 효율적이다.
– Physical server 100, 200 대 정도는 cloud 가 필요 없다.
• Cloud management system 간의 성능 비교는 무의미하다.
– VM의 성능은 H/W, Hypervisor, OS 에 영향을 받는다.
21. 별첨 : OpenStack에서 사용한 Python 활용법
Seungkyu Ahn
John Haan
Yoon Doyoul
Sean Lee
Hyangii
Inhye Park
Joseph Park (etlars@gmail.com)
22. OpenStack Application Architecture
• 프로젝트 간 통신 : Rest API (HTTP Request)
• 프로젝트 내 프로세스(모듈) 간 통신 : AMQP (Advanced Message Queuing Protocol)
Nova-API
Nova-Scheduler
Queu
e
Nova-Compute
Glance-API
Rest API
AMQP
Python-glanceclient
Nova-Conductor
from nova.compute import api
self._api = api.API()
AMQP
- nova
- api
- compute
- api.py
- manager.py
23. OpenStack Application Architecture (계속)
• API 패키지 : 프로세스 모듈 (nova-api)
• api.py : 다른 프로세스 queue 호출을 위한 파일
(nova-conductor nova-compute)
• manager.py : queue subscribe 파일
(nova-conductor nova-
compute)
25. Data Access Object (DAO)
• Nova.db.base.Base
def __init__(…):
self.db = import_module(db_driver)
• nova.db.api.py
_BACKEND_MAPPING = {‘sqlalchemy’:
‘nova.db.sqlalchemy.api’}
IMPL = concurrency.TpoolDbapiWrapper(CONF,
backend_mapping=_BACKEND_MAPPING )
…
def instance_update(…)
IMPL.instance_update(…)
• Manager(base.Base)
…
self.db.instance_update(…)
db_driver 는 “nova.db” 패키지
nova.db 패키지의 __init__ 은 from nova.db.api import *
그러므로 self.db = nova.db.api
26. Configuration 활용
• nova.db.sqlalchemy.api.py
Import oslo.config import cfg
…
CONF = cfg.CONF
CONF.compute_topic 과 같이 사용
• oslo.config.cfg.py
CONF = ConfigOpts()
• Opt
name = 이름
type = StrOpt, IntOpt, FloatOpt, BoolOpt, List, DickOpt, IPOpt, MultiOpt, MultiStrOpt
dest = ConfigOpts property 와 대응되는 이름
default = 기본값
• ConfigOpts(collections.Mapping)
def __init__(self):
self._opts = {} # dict of dicts of (opt:, override:, default: )
def __getattr__ (self.name): # property 가 실제 존재하지 않으면 호출
됨
return self._get(name)
27. Policy administration for OpenStack (1/3)
Keystone을 통해 사용자별 role을 부여.
policy.json 파일을 통해 API 별로 실행할 수 있는 role을 구분.
nova 및 cinder 소스에서 실제 API가 수행 되기 전 policy를 검사하는 기능.
[예] nova의 shelve 기능
# keystone
- keystone을 통해 user를 생성
- 부여할 role을 생성
- user에 role을 부여
root@MGMT-SET2:~# keystone role-list
+----------------------+----------------------
+
| id | name |
+----------------------+----------------------
+
| admin | admin |
| user | user |
+----------------------+----------------------
+
root@MGMT-SET2:~# keystone user-role-list
+-------+-------+----------------------------------+--------------------
--------------+
| id | name | user_id | tenant_id
|
+-------+-------+----------------------------------+--------------------
--------------+
| admin | admin | 210b71.. | 7559375.. |
| user | user | 210b71.. | 7559375.. |
+-------+-------+----------------------------------+--------------------
--------------+
1. Role 정의 2. user 마다 role을 적용
John Haan
28. # nova
- /etc/nova/policy.json 에 API 별 role 권한을 설정
- /nova/policy.py에서 API action을 check
Policy administration for OpenStack (2/3)
def check_policy(context, action, target, scope='compute'):
_action = '%s:%s' % (scope, action)
nova.policy.enforce(context, _action, target)
"context_is_admin": "role:admin",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s”,
…
"compute:shelve": " admin_or_owner ",
29. Policy administration for OpenStack (3/3)
# nova
- nova/compute/api.py에서 policy를 check하는 decorator 함수를 정의
- shelve method 앞에 policy check 함수를 적용
def policy_decorator(scope):
"""Check corresponding policy prior of wrapped method to execution."""
def outer(func):
@functools.wraps(func)
def wrapped(self, context, target, *args, **kwargs):
check_policy(context, func.__name__, target, scope)
return func(self, context, target, *args, **kwargs)
return wrapped
return outer
wrap_check_policy = policy_decorator(scope='compute')
@wrap_check_policy
def shelve(self, context, instance):
"""Shelve an instance.
.
.
30. Decorator Function in OpenStack (1/3)
데코레이터 함수란?
- 함수 자체를 인자로 받아서 원래의 함수는 변경하지 않고 다른 기능 들을 추가해주는 기능
import time
def elapsed_time(func):
def decorated(*args, **kwargs):
start = time.time()
func(args, kwargs)
end = time.time()
print "Elapsed time: %f" % (end-
start)
return decorated
@elapsed_time
def hello():
print 'hello'
Example
Return response Get ref, a,b,c
ref(a1,b1,c1)Get response
Bypass the actual
function
Caller
Call Function(a,b,c)
Statements
Actual Function
--
Return response
ProcessProcess Decorator
▶ elapsed_time() 함수가 hello() 함수 기
능 자체를 변경하지 않고, decorator로 앞
뒤에 시간을 기록해 준다.
John Haan
31. Decorator Function in OpenStack (2/3)
OpenStack에서의 활용
- API 함수가 실행되기 전에 check 하는 기능을 decorator로 추가
- 예를 들어, shelve() 함수가 수행되기 전에 instance의 lock 여부를 decorator로 check
[대상 API 함수 ]
[ decorator 함수 ]
@check_instance_lock
def shelve(self, context, instance):
"""Shelve an instance.
def check_instance_lock(function):
def inner(self, context, instance, *args, **kwargs):
if instance['locked'] and not context.is_admin:
raise exception.InstanceIsLocked(instance_uuid=
instance['uuid'])
return function(self, context, instance, *args,
**kwargs)
return inner
▶ nova의 API에서 각 method가 실
행되기 전에 decorator 함수를 통해
check 기능을 수행한다.
▶ API method를 인자로 받고 내부
method(inner)를 호출하고 그 안에
서 instance가 locked 되어 있으면
exception 처리를 해준다.
32. Decorator Function in OpenStack (3/3)
• def require_admin_context(f):
def wrapper(*args, **kwargs):
nova.context.require_admin_context(args[0])
return f(*args, **kwargs)
return wrapper
• @require_admin_context
def service_get_by_compute_host(context, host):
…
• OOP Decorator 패턴과 비교
GoF 의 Decorator 와는 조금 다른 방법
GoF Decorator 패턴 : 상속을 받으면서 Decorate 를 추가하는 방법 (OOP)
GoF 의 Template method pattern 과 오히려 더 유사 (이것 역시 OOP 적임)
AspectJ 혹은 Spring AOP (Aspect Oriented Programming) 와 유사 (Python 이 더 간단함)
33. Routes.Mapper.resource (1/2)
• REST API Request와 API module의 Controller를 mapping
• routes.mapper.resource는 단 몇 줄로 REST API(GET, POST, PUT, DELETE)에 대한 mapper를 손쉽게 등록 가능
• 아래 예제는 resource에 tests를 등록하고 tests로 REST API Request가 들어올 경우 testController로 전달
>>> from routes import mapper
>>> test_map = Mapper()
>>> test_map.resource("test", "tests", controller="testController")
>>> print test_map
Route name Methods Path
POST /tests.:(format)
POST /tests
formatted_tests GET /tests.:(format)
tests GET /tests
formatted_new_test GET /tests/new.:(format)
new_test GET /tests/new
PUT /tests/:(id).:(format)
PUT /tests/:(id)
DELETE /tests/:(id).:(format)
DELETE /tests/:(id)
formatted_edit_test GET /tests/:(id)/edit.:(format)
edit_test GET /tests/:(id)/edit
formatted_test GET /tests/:(id).:(format)
test GET /tests/:(id)
Yoon Doyoul
34. Routes.Mapper.resource (2/2)
if init_only is None or 'limits' in init_only:
self.resources['limits'] = limits.create_resource()
mapper.resource("limit", "limits",
controller=self.resources['limits'])
if init_only is None or 'flavors' in init_only:
self.resources['flavors'] = flavors.create_resource()
mapper.resource("flavor", "flavors",
controller=self.resources['flavors'],
collection={'detail': 'GET'},
member={'action': 'POST'})
• Openstack Nova에서의 Mapper 사용
• nova-api service가 로딩되면서 API에 정의된
모든controller들을 mapper에 등록
• 기본 정의된 API 뿐만 아니라 nova/api/openstack/compute/contrib 디
렉토리 밑에 정의된 controller들도 자동으로 등록
nova/api/openstack/compute/__init__.py
35. API Extensions (1/2)
• Openstack Nova API는 extensions API라는 명칭으로 손쉽게 API 추가 가능
• nova/api/openstack/contirb 디렉토리 밑에 정해진 형식에 맞춰 API를 추가하게 되면 자동
으로 인식하여 등록됨
• /contrib 디렉토리 밑에 파일을 생성하고, nova.api.openstack.extensions의
ExtensionDescriptor 객체를 상속받는 class를 만들고, 그에 맞는 Controller 부분 구현 필요
• nova-api service가 로딩되면서 ExtensionManager를 실행하고 /contrib 디렉토리 밑에 정의
된 파일들을 API에 자동으로 등록하는 방식
Yoon Doyoul
36. API Extensions (2/2)
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
class TestController(object):
def create(self, req, body):
def delete(self, req, id):
def show(self, req, id):
def index(self, req):
class TempController(wsgi.Controller):
@wsgi.action(‘os-stop’)
def _stop_test(self, req, id, body):
class Tests(extensions.ExtensionDescriptor):
"""Test Code."""
name = "Tests"
alias = "os-tests"
def get_resources(self):
resources = []
res = extensions.ResourceExtension('os-tests',TestController())
resources.append(res)
return resources
def get_controller_extensions(self):
controller = TempController()
extension = extensions.ControllerExtension(self, ‘os-tests', controller)
return [extension]
37. Usages Pipeline for OpenStack API Servers(1/3)
1. 사용자의 실제 Request를 처리하기 이전 Pre-Process를 수행한다.
2. API 서버의 필요자원 사전 Load
3. Rate-limit, Health-Check같은 부가기능 구현
API
Server
Rest Request
인증 로그
제약
사항
Filter
Req 1
Req 2
Req 3
Req 4
Req ...
Cache Meta
요청
제한 …
?
? ?
DB LDAP
Sean Lee
38. Usages Pipeline for OpenStack API Servers(2/3)
OpenStack API 에서의 활용
- api-paste.ini파일에 사용할 각각의 Filter를 선언하고, 이에 대한 Pipeline을 구성하여 차례대로 처
리
- 어떠한 Pipeline을 사용할 지에 대해서는 각 OpenStack Component의 configuration에서 설정
ex) auth_strategy = keystone
[예]api-paste.ini for Nova
[예]Pipeline에 선언된 Filter
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap ratelimit sizelimit authtoken keystonecontext ratelimit
osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
limits =(POST, "*", .*, 15, MINUTE);(POST, "*/servers", ^/servers, 50, DAY);(PUT, "*", .*, 10,
MINUTE);(GET, "*changes-since*", .*changes-since.*, 3, MINUTE);(DELETE, "*",.*, 100, MINUTE)
[filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
39. Usages Pipeline for OpenStack API Servers(3/3)
def pipeline_factory(loader, global_conf, **local_conf):
"""A paste pipeline replica that keys off of auth_strategy."""
pipeline = local_conf[CONF.auth_strategy]
if not CONF.api_rate_limit:
limit_name = CONF.auth_strategy + '_nolimit'
pipeline = local_conf.get(limit_name, pipeline)
pipeline = pipeline.split()
return _load_pipeline(loader, pipeline)
def _load_pipeline(loader, pipeline):
filters = [loader.get_filter(n) for n in pipeline[:-1]]
app = loader.get_app(pipeline[-1])
filters.reverse()
for filter in filters:
app = filter(app)
return app
class RequestBodySizeLimiter(wsgi.Middleware):
"""Limit the size of incoming requests."""
def __init__(self, *args, **kwargs):
super(RequestBodySizeLimiter, self).__init__(*args, **kwargs)
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
if req.content_length > CONF.osapi_max_request_body_size:
msg = _("Request is too large.")
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
if req.content_length is None and req.is_body_readable:
limiter = LimitingReader(req.body_file,
CONF.osapi_max_request_body_size)
req.body_file = limiter
return self.application
nova/api/auth.py nova/api/sizelimit.py
1. use=call:nova.api.auth:pipeline_factory 실제 nova/api/auth.py의 pipeline_factory 호출
2. Configuration의 ‘auth_strategy’값을 load하여 어떠한 pipeline을 사용할지 결정
3. Pipeline에 선언된 순서 별로 각각의 filter_factory load
ex) paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
실제 nova/api/sizelimit.py의 RequestBodySizeLimiter class 호출
40. Lambda in Python (1/3)
• 축약함수, 이름이 없는 함수
• 일반적인 def의 경우 함수 이름을 정하고, 이를 나중에 재사용
• Lambda의 경우 이름을 정하지 않고, 한 줄에 함수를 정의함.
>>> square = lambda x: x*x*x
>>> cube = lambda x: x*x*x*x
>>> print square(2)
8
>>> print cube(2)
16
Hyangii
41. Lambda in Python (2/3)
• 인자 없는 Lambda
• Lazy Evaluation, 계산이 필요한 경우에만 호출할 때 사용
x = lambda: sum(range(1, 4))
print x()
42. Lambda in Python (3/3)
• Token 획득을 위해 인자 없는 lambda 사용
• Ceilometerclient/client.py
def _do_authenticate(self, http_client):
token = self.opts.get('token') or self.opts.get('auth_token')
endpoint = self.opts.get('endpoint')
if not (token and endpoint):
project_id = (self.opts.get('project_id') or
self.opts.get('tenant_id'))
project_name = (self.opts.get('project_name') or
self.opts.get('tenant_name'))
ks_kwargs = {
…
}
# retrieve session
ks_session = _get_keystone_session(**ks_kwargs)
token = lambda: ks_session.get_token()
endpoint = (self.opts.get('endpoint') or
_get_endpoint(ks_session, **ks_kwargs))
self.opts['token'] = token
self.opts['endpoint'] = endpoint
def token_and_endpoint(self, endpoint_type,
service_type
token = self.opts.get('token')
if callable(token):
token = token()
return token, self.opts.get('endpoint')
43. Load Drivers/Extensions/Filter using stevedore (1/3)
OpenStack stevedore Library 를 사용하여 추가로 Driver, Extension API, Filter를 구현하
기 위한 방법
step 1 : 새로 추가될 Driver 파일을 구현한다.
.
step 2 : 새로 추가될 Extension/Filter 파일을 구현한다.
.
# cinder/volume/driver/new_driver.py
class SimpleDriver:
def get_name(self):
return “ This is Simple Driver”
# cinder/scheduler/filters/new_filter.py
class SimpleFilter:
def get_name(self):
return “ This is Simple Filter”
Inhye Park
44. step 3 :앞에서 구현한 파일을 Stevedore 를 사용하여 Load 시킨다. .
.
- namespace가 “cinder.volume.driver”인 Driver “simple_driver"를 로딩한다는 것을 의미함
- namespace가 “cinder.scheduler.filters”인 Filter “simple_filter"를 로딩한다는 것을 의미함
step 4 : setup.cfg 파일에 새로 추가될 Extension/Filter 정보를 연결해준다.
.
Load Drivers/Extensions/Filter using stevedore (2/3)
from stevedore import driver
from stevedore import extension
mydriver = driver.DriverManager(namespace=“cinder.volume.driver", name='simple_driver')
myextension = extension.ExtensionManager(namespace=“cinder.scheduler.filters“, name=‘simple_filter’)
#setup.cfg
[metadata]
name = cinder
[files]
packages = cinder
[entry_points]
cinder.volume.driver =
simple_driver = cinder.volume.driver.new_driver:SimpleDriver
cinder.scheduler.filters =
simple_filter = cinder.scheduler.filters.new_filter:SimpleFilter
45. step 5 :해당 프로젝트를 소스로 인스톨시킨다.
.
step 6 : 소스 디렉토리에 다음과 같이 entry_points가 자동으로 생성된다.
step 7 : 새로 구현한 filter는 config에서 지정하여 사용한다.
Load Drivers/Extensions/Filter using stevedore (3/3)
# cd cinder
# python setup.py install
# /usr/lib/python2.7/dist-packages/cinder-2014.1.3.egg-info/entry_points.txt
[cinder.volume.driver]
simple_driver = cinder.volume.driver.new_driver:SimpleDriver
[cinder.scheduler.filters] =
simple_filter = cinder.scheduler.filters.new_filter:SimpleFilter
# /etc/cinder/cinder.conf
Scheduler_default_filters = simple_filter
46. neutron DVR (distributed virtual router) (1/5)
before DVR: qRouter is only on network node
external
network
node
qROUTER
network1 network2
Joseph Park
48. neutron DVR (distributed virtual router) (3/5)
CN1
Neutron
Server
user
(CLI)
CN1CN1
add new routers
add router ports to br-int
[message queue]
[DEFAULT]
debug = True
l3_agents_per_router = 3
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
ovs_use_veth = False
use_namespaces = True
external_network_bridge = br-ex
agent_mode = dvr
l3_agent_manager = neutron.agent.l3_agent.L3NATAgentWithState
Report
vrrp_confs = $state_path/vrrp
and gather subnet info
[ neutron/agent/l3/agent.py ]
def router_added_to_agent(self, context, payload):
LOG.debug('Got router added to agent :%r', payload)
self.routers_updated(context, payload)
…
def routers_updated(self, context, routers):
"""Deal with routers modification and creation RPC message."""
LOG.debug('Got routers updated notification :%s', routers)
if routers:
# This is needed for backward compatibility
if isinstance(routers[0], dict):
routers = [router['id'] for router in routers]
for id in routers:
update = queue.RouterUpdate(id, queue.PRIORITY_RPC)
self._queue.add(update)
[ neutron/agent/l3/agent.py ]
def get_ports_by_subnet(self, context, **kwargs):
"""DVR: RPC called by dvr-agent to get all ports for subnet."""
subnet_id = kwargs.get('subnet_id')
LOG.debug("DVR: subnet_id: %s", subnet_id)
filters = {'fixed_ips': {'subnet_id': [subnet_id]}}
return self.plugin.get_ports(context, filters=filters)
49. neutron DVR (distributed virtual router) (4/5)
compute node 1
ethNethX
br-tun
qROUTER
br-ex
VM11
2
with f
ip
VM12
1
br-int
30.1.0.31to NETNODE
to INTERNET
FIP
VM112 : 30.1.1.3 with floating ip 10.1.0.14
5
VM121 : 30.1.2.7 without floating ip
VM with floating ip sends all packets tow
ard outside via FIP agent(, br-ex and ethX
)
VM without floating ip sends all packets t
oward outside via SNAT agent
CN1 configured
L3: agent_mode = dvr
OVS: enable_distributed_routing = True
50. neutron DVR (distributed virtual router) (5/5)
compute node 1 network node
ethNethX
br-tun
qROUTER
br-ex
VM112
with fip
VM121
br-int
to NETNODE
to INTERNET
FIP
ethX
br-tun
br-ex
br-int
NETethN
qDHCP
to INTERNET
qROUTER SNAT
NETWORK node configured
agent_mode = dvr_snat
OVS: enable_distributed_routing = True
packet flow without fix
packet flow with fip