3 # Copyright 2017 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 gi
.require_version('RwDts', '1.0')
24 from gi
.repository
import (
29 from rift
.mano
.utils
.project
import get_add_delete_update_cfgs
31 from . import accounts
34 class SDNAccountNotFound(Exception):
38 class SDNAccountError(Exception):
42 class SDNAccountConfigCallbacks(object):
44 on_add_apply
=None, on_add_prepare
=None,
45 on_delete_apply
=None, on_delete_prepare
=None):
48 def prepare_noop(*args
, **kwargs
):
51 def apply_noop(*args
, **kwargs
):
54 self
.on_add_apply
= on_add_apply
55 self
.on_add_prepare
= on_add_prepare
56 self
.on_delete_apply
= on_delete_apply
57 self
.on_delete_prepare
= on_delete_prepare
59 for f
in ('on_add_apply', 'on_delete_apply'):
60 ref
= getattr(self
, f
)
62 setattr(self
, f
, apply_noop
)
65 if asyncio
.iscoroutinefunction(ref
):
66 raise ValueError('%s cannot be a coroutine' % (f
,))
68 for f
in ('on_add_prepare', 'on_delete_prepare'):
69 ref
= getattr(self
, f
)
71 setattr(self
, f
, prepare_noop
)
74 if not asyncio
.iscoroutinefunction(ref
):
75 raise ValueError("%s must be a coroutine" % f
)
78 class SDNAccountConfigSubscriber(object):
79 XPATH
= "C,/rw-sdn:sdn/rw-sdn:account"
81 def __init__(self
, dts
, log
, rwlog_hdl
, sdn_callbacks
, acctstore
):
84 self
._rwlog
_hdl
= rwlog_hdl
87 self
.accounts
= acctstore
89 self
._sdn
_callbacks
= sdn_callbacks
91 def add_account(self
, account_msg
):
92 self
._log
.info("adding sdn account: {}".format(account_msg
))
94 account
= accounts
.SDNAccount(self
._log
, self
._rwlog
_hdl
, account_msg
)
95 self
.accounts
[account
.name
] = account
97 self
._sdn
_callbacks
.on_add_apply(account
)
99 def delete_account(self
, account_name
):
100 self
._log
.info("deleting sdn account: {}".format(account_name
))
101 del self
.accounts
[account_name
]
103 self
._sdn
_callbacks
.on_delete_apply(account_name
)
105 def update_account(self
, account_msg
):
106 """ Update an existing sdn account
108 In order to simplify update, turn an update into a delete followed by
109 an add. The drawback to this approach is that we will not support
110 updates of an "in-use" sdn account, but this seems like a
111 reasonable trade-off.
115 account_msg - The sdn account config message
117 self
._log
.info("updating sdn account: {}".format(account_msg
))
119 self
.delete_account(account_msg
.name
)
120 self
.add_account(account_msg
)
122 def deregister(self
):
124 self
._reg
.deregister()
129 def apply_config(dts
, acg
, xact
, action
, _
):
130 self
._log
.debug("Got sdn account apply config (xact: %s) (action: %s)", xact
, action
)
132 if xact
.xact
is None:
133 if action
== rwdts
.AppconfAction
.INSTALL
:
134 curr_cfg
= self
._reg
.elements
136 self
._log
.debug("SDN account being re-added after restart.")
137 if not cfg
.has_field('account_type'):
138 raise SDNAccountError("New SDN account must contain account_type field.")
139 self
.add_account(cfg
)
141 # When RIFT first comes up, an INSTALL is called with the current config
142 # Since confd doesn't actally persist data this never has any data so
144 self
._log
.debug("No xact handle. Skipping apply config")
148 add_cfgs
, delete_cfgs
, update_cfgs
= get_add_delete_update_cfgs(
149 dts_member_reg
=self
._reg
,
155 for cfg
in delete_cfgs
:
156 self
.delete_account(cfg
.name
)
160 self
.add_account(cfg
)
163 for cfg
in update_cfgs
:
164 self
.update_account(cfg
)
167 def on_prepare(dts
, acg
, xact
, xact_info
, ks_path
, msg
, scratch
):
168 """ Prepare callback from DTS for SDN Account """
170 action
= xact_info
.query_action
171 self
._log
.debug("SDN account on_prepare config received (action: %s): %s",
172 xact_info
.query_action
, msg
)
174 if action
in [rwdts
.QueryAction
.CREATE
, rwdts
.QueryAction
.UPDATE
]:
175 if msg
.name
in self
.accounts
:
176 self
._log
.debug("SDN account already exists. Invoking update request")
178 # Since updates are handled by a delete followed by an add, invoke the
179 # delete prepare callbacks to give clients an opportunity to reject.
180 yield from self
._sdn
_callbacks
.on_delete_prepare(msg
.name
)
183 self
._log
.debug("SDN account does not already exist. Invoking on_prepare add request")
184 if not msg
.has_field('account_type'):
185 raise SDNAccountError("New sdn account must contain account_type field.")
187 account
= accounts
.SDNAccount(self
._log
, self
._rwlog
_hdl
, msg
)
188 yield from self
._sdn
_callbacks
.on_add_prepare(account
)
190 elif action
== rwdts
.QueryAction
.DELETE
:
191 # Check if the entire SDN account got deleted
192 fref
= ProtobufC
.FieldReference
.alloc()
193 fref
.goto_whole_message(msg
.to_pbcm())
194 if fref
.is_field_deleted():
195 yield from self
._sdn
_callbacks
.on_delete_prepare(msg
.name
)
198 self
._log
.error("Deleting individual fields for SDN account not supported")
199 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
203 self
._log
.error("Action (%s) NOT SUPPORTED", action
)
204 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
206 xact_info
.respond_xpath(rwdts
.XactRspCode
.ACK
)
208 self
._log
.debug("Registering for SDN Account config using xpath: %s",
209 SDNAccountConfigSubscriber
.XPATH
,
212 acg_handler
= rift
.tasklets
.AppConfGroup
.Handler(
213 on_apply
=apply_config
,
216 with self
._dts
.appconf_group_create(acg_handler
) as acg
:
217 self
._reg
= acg
.register(
218 xpath
=SDNAccountConfigSubscriber
.XPATH
,
219 flags
=rwdts
.Flag
.SUBSCRIBER | rwdts
.Flag
.DELTA_READY | rwdts
.Flag
.CACHE
,
220 on_prepare
=on_prepare
,