3
from landscape.lib.network import get_fqdn
5
from landscape.ui.constants import CANONICAL_MANAGED, NOT_MANAGED
6
from landscape.ui.model.configuration.proxy import ConfigurationProxy
9
HOSTED_LANDSCAPE_HOST = "landscape.canonical.com"
10
LOCAL_LANDSCAPE_HOST = ""
12
HOSTED_ACCOUNT_NAME = ""
13
LOCAL_ACCOUNT_NAME = "standalone"
20
MANAGEMENT_TYPE = "management-type"
21
COMPUTER_TITLE = "computer-title"
22
LANDSCAPE_HOST = "landscape-host"
23
ACCOUNT_NAME = "account-name"
28
MANAGEMENT_TYPE: NOT_MANAGED,
29
COMPUTER_TITLE: get_fqdn(),
31
LANDSCAPE_HOST: HOSTED_LANDSCAPE_HOST,
32
ACCOUNT_NAME: HOSTED_ACCOUNT_NAME,
33
PASSWORD: HOSTED_PASSWORD},
35
LANDSCAPE_HOST: LOCAL_LANDSCAPE_HOST,
36
ACCOUNT_NAME: LOCAL_ACCOUNT_NAME,
37
PASSWORD: LOCAL_PASSWORD}}
40
def derive_server_host_name_from_url(url):
42
Extract the hostname part from a URL.
45
without_protocol = url[url.index("://") + 3:]
47
without_protocol = url
49
return without_protocol[:without_protocol.index("/")]
51
return without_protocol
54
def derive_url_from_host_name(host_name):
56
Extrapolate a url from a host name.
58
#Reuse this code to make sure it's a proper host name
59
host_name = derive_server_host_name_from_url(host_name)
60
return "https://" + host_name + "/message-system"
63
def derive_ping_url_from_host_name(host_name):
65
Extrapolate a ping_url from a host name.
67
#Reuse this code to make sure it's a proper host name
68
host_name = derive_server_host_name_from_url(host_name)
69
return "http://" + host_name + "/ping"
72
class StateError(Exception):
74
An exception that is raised when there is an error relating to the current
79
class TransitionError(Exception):
81
An L{Exception} that is raised when a valid transition between states fails
82
for some non state related reason. For example, this error is raised when
83
the user does not have the privilege of reading the configuration file,
84
this causes the transition from L{VirginState} to L{InitialisedState} to
85
fail but not because that transition from one state to another was not
86
permitted, but rather the transition encountered an error.
90
class ConfigurationState(object):
92
Base class for states used in the L{ConfigurationModel}.
94
def __init__(self, data, proxy, uisettings):
95
self._data = copy.deepcopy(data)
97
self._uisettings = uisettings
99
def get_config_filename(self):
100
return self._proxy.get_config_filename()
102
def get(self, *args):
104
Retrieve only valid values from two level dictionary based tree.
106
This mainly served to pick up programming errors and could easily be
107
replaced with a simpler scheme.
110
if arglen > 2 or arglen == 0:
112
"get() takes either 1 or 2 keys (%d given)" % arglen)
113
if arglen == 2: # We're looking for a leaf on a branch
115
if args[0] in [HOSTED, LOCAL]:
116
sub_dict = self._data.get(args[0], {})
117
sub_dict = self._data[args[0]]
118
if not isinstance(sub_dict, dict):
120
"Compound key [%s][%s] is invalid. The data type " +
121
"returned from the first index was %s." %
122
sub_dict.__class__.__name__)
123
return sub_dict.get(args[1], None)
125
if args[0] in (MANAGEMENT_TYPE, COMPUTER_TITLE):
126
return self._data.get(args[0], None)
128
raise KeyError("Key [%s] is invalid. " % args[0])
130
def set(self, *args):
132
Set only valid values from two level dictionary based tree.
134
This mainly served to pick up programming errors and could easily be
135
replaced with a simpler scheme.
138
if arglen < 2 or arglen > 3:
139
raise TypeError("set() takes either 1 or 2 keys and exactly 1 " +
140
"value (%d arguments given)" % arglen)
141
if arglen == 2: # We're setting a leaf attached to the root
142
self._data[args[0]] = args[1]
143
else: # We're setting a leaf on a branch
145
if args[0] in [HOSTED, LOCAL]:
146
sub_dict = self._data.get(args[0], {})
147
if not isinstance(sub_dict, dict):
148
raise KeyError("Compound key [%s][%s] is invalid. The data " +
149
"type returned from the first index was %s."
150
% sub_dict.__class__.__name__)
151
sub_dict[args[1]] = args[2]
152
self._data[args[0]] = sub_dict
154
def load_data(self, asynchronous=True, exit_method=None):
155
raise NotImplementedError
158
raise NotImplementedError
161
raise NotImplementedError
164
raise NotImplementedError
166
def exit(self, asynchronous=True, exit_method=None):
167
return ExitedState(self._data, self._proxy, self._uisettings,
168
asynchronous=asynchronous, exit_method=exit_method)
171
class Helper(object):
173
Base class for all state transition helpers.
175
It is assumed that the Helper classes are "friends" of the
176
L{ConfigurationState} classes and can have some knowledge of their
177
internals. They shouldn't be visible to users of the
178
L{ConfigurationState}s and in general we should avoid seeing the
179
L{ConfigurationState}'s _data attribute outside this module.
182
def __init__(self, state):
186
class ModifiableHelper(Helper):
188
Allow a L{ConfigurationState}s to be modified.
192
return ModifiedState(self._state._data, self._state._proxy,
193
self._state._uisettings)
196
class UnloadableHelper(Helper):
198
Disallow loading of data into a L{ConfigurationModel}.
201
def load_data(self, asynchronous=True, exit_method=None):
202
raise StateError("A ConfiguratiomModel in a " +
203
self.__class__.__name__ +
204
" cannot be transitioned via load_data()")
207
class UnmodifiableHelper(Helper):
209
Disallow modification of a L{ConfigurationState}.
213
raise StateError("A ConfigurationModel in " +
214
self.__class__.__name__ +
215
" cannot transition via modify()")
218
class RevertableHelper(Helper):
220
Allow reverting of a L{ConfigurationModel}.
224
return InitialisedState(self._state._data, self._state._proxy,
225
self._state._uisettings)
228
class UnrevertableHelper(Helper):
230
Disallow reverting of a L{ConfigurationModel}.
234
raise StateError("A ConfigurationModel in " +
235
self.__class__.__name__ +
236
" cannot transition via revert()")
239
class PersistableHelper(Helper):
241
Allow a L{ConfigurationModel} to persist.
244
def _save_to_uisettings(self):
246
Persist full content to the L{UISettings} object.
248
self._state._uisettings.set_management_type(
249
self._state.get(MANAGEMENT_TYPE))
250
self._state._uisettings.set_computer_title(
251
self._state.get(COMPUTER_TITLE))
252
self._state._uisettings.set_hosted_account_name(
253
self._state.get(HOSTED, ACCOUNT_NAME))
254
self._state._uisettings.set_hosted_password(
255
self._state.get(HOSTED, PASSWORD))
256
self._state._uisettings.set_local_landscape_host(
257
self._state.get(LOCAL, LANDSCAPE_HOST))
258
self._state._uisettings.set_local_account_name(
259
self._state.get(LOCAL, ACCOUNT_NAME))
260
self._state._uisettings.set_local_password(
261
self._state.get(LOCAL, PASSWORD))
263
def _save_to_config(self):
265
Persist the subset of the data we want to make live to the actual
268
hosted = self._state.get(MANAGEMENT_TYPE)
269
if hosted is NOT_MANAGED:
272
if hosted == CANONICAL_MANAGED:
276
self._state._proxy.url = derive_url_from_host_name(
277
self._state.get(first_key, LANDSCAPE_HOST))
278
self._state._proxy.ping_url = derive_ping_url_from_host_name(
279
self._state.get(first_key, LANDSCAPE_HOST))
280
self._state._proxy.account_name = self._state.get(
281
first_key, ACCOUNT_NAME)
282
self._state._proxy.registration_password = self._state.get(
284
self._state._proxy.computer_title = self._state.get(COMPUTER_TITLE)
285
self._state._proxy.write()
288
self._save_to_uisettings()
289
self._save_to_config()
290
return InitialisedState(self._state._data, self._state._proxy,
291
self._state._uisettings)
294
class UnpersistableHelper(Helper):
296
Disallow persistence of a L{ConfigurationModel}.
300
raise StateError("A ConfiguratonModel in " +
301
self.__class__.__name__ +
302
" cannot be transitioned via persist().")
305
class ExitedState(ConfigurationState):
307
The terminal state of L{ConfigurationModel}, you can't do anything further
308
once this state is reached.
310
def __init__(self, data, proxy, uisettings, exit_method=None,
312
super(ExitedState, self).__init__(None, None, None)
313
if callable(exit_method):
316
proxy.exit(asynchronous=asynchronous)
317
self._unloadable_helper = UnloadableHelper(self)
318
self._unmodifiable_helper = UnmodifiableHelper(self)
319
self._unrevertable_helper = UnrevertableHelper(self)
320
self._unpersistable_helper = UnpersistableHelper(self)
322
def load_data(self, asynchronous=True, exit_method=None):
323
return self._unloadable_helper.load_data(asynchronous=asynchronous,
324
exit_method=exit_method)
327
return self._unmodifiable_helper.modify()
330
return self._unrevertable_helper.revert()
333
return self._unpersistable_helper.persist()
335
def exit(self, asynchronous=True):
339
class ModifiedState(ConfigurationState):
341
The state of a L{ConfigurationModel} whenever the user has modified some
342
data but hasn't yet L{persist}ed or L{revert}ed.
345
def __init__(self, data, proxy, uisettings):
346
super(ModifiedState, self).__init__(data, proxy, uisettings)
347
self._modifiable_helper = ModifiableHelper(self)
348
self._revertable_helper = RevertableHelper(self)
349
self._persistable_helper = PersistableHelper(self)
352
return self._modifiable_helper.modify()
355
return self._revertable_helper.revert()
358
return self._persistable_helper.persist()
361
class InitialisedState(ConfigurationState):
363
The state of the L{ConfigurationModel} as initially presented to the
364
user. Baseline data should have been loaded from the real configuration
365
data, any persisted user data should be loaded into blank values and
366
finally defaults should be applied where necessary.
369
def __init__(self, data, proxy, uisettings):
370
super(InitialisedState, self).__init__(data, proxy, uisettings)
371
self._modifiable_helper = ModifiableHelper(self)
372
self._unrevertable_helper = UnrevertableHelper(self)
373
self._unpersistable_helper = UnpersistableHelper(self)
374
self._load_uisettings_data()
375
if not self._load_live_data():
376
raise TransitionError("Authentication Failure")
378
def _load_uisettings_data(self):
380
Load the complete set of dialog data from L{UISettings}.
382
hosted = self._uisettings.get_management_type()
383
self.set(MANAGEMENT_TYPE, hosted)
384
computer_title = self._uisettings.get_computer_title()
386
self.set(COMPUTER_TITLE, computer_title)
387
self.set(HOSTED, ACCOUNT_NAME,
388
self._uisettings.get_hosted_account_name())
389
self.set(HOSTED, PASSWORD, self._uisettings.get_hosted_password())
390
self.set(LOCAL, LANDSCAPE_HOST,
391
self._uisettings.get_local_landscape_host())
392
local_account_name = self._uisettings.get_local_account_name()
393
if local_account_name:
394
self.set(LOCAL, ACCOUNT_NAME, local_account_name)
395
self.set(LOCAL, PASSWORD, self._uisettings.get_local_password())
397
def _load_live_data(self):
399
Load the current live subset of data from the configuration file.
401
if self._proxy.load(None):
402
computer_title = self._proxy.computer_title
404
self.set(COMPUTER_TITLE, computer_title)
405
url = self._proxy.url
406
if url.find(HOSTED_LANDSCAPE_HOST) > -1:
407
self.set(HOSTED, ACCOUNT_NAME, self._proxy.account_name)
408
self.set(HOSTED, PASSWORD, self._proxy.registration_password)
410
self.set(LOCAL, LANDSCAPE_HOST,
411
derive_server_host_name_from_url(url))
412
if self._proxy.account_name != "":
413
self.set(LOCAL, ACCOUNT_NAME, self._proxy.account_name)
418
def load_data(self, asynchronous=True, exit_method=None):
422
return self._modifiable_helper.modify()
425
return self._unrevertable_helper.revert()
428
return self._unpersistable_helper.persist()
431
class VirginState(ConfigurationState):
433
The state of the L{ConfigurationModel} before any actions have been taken
437
def __init__(self, proxy, uisettings):
438
super(VirginState, self).__init__(DEFAULT_DATA, proxy, uisettings)
439
self._unmodifiable_helper = UnmodifiableHelper(self)
440
self._unrevertable_helper = UnrevertableHelper(self)
441
self._unpersistable_helper = UnpersistableHelper(self)
443
def load_data(self, asynchronous=True, exit_method=None):
445
return InitialisedState(self._data, self._proxy, self._uisettings)
446
except TransitionError:
447
return ExitedState(self._data, self._proxy, self._uisettings,
448
asynchronous=asynchronous,
449
exit_method=exit_method)
452
return self._unmodifiable_helper.modify()
455
return self._unrevertable_helper.revert()
458
return self._unpersistable_helper.persist()
461
class ConfigurationModel(object):
463
L{ConfigurationModel} presents a model of configuration as the UI
464
requirements describe it (separate values for the Hosted and Local
465
configurations) as opposed to the real structure of the configuration
466
file. This is intended to achieve the following:
468
1. Allow the expected behaviour in the UI without changing the live
470
2. Supersede the overly complex logic in the controller layer with a
471
cleaner state pattern.
473
The allowable state transitions are:
475
VirginState --(load_data)--> InitialisedState
476
VirginState --(load_data)--> ExitedState
477
VirginState --(exit)-------> ExitedState
478
InitialisedState --(modify)-----> ModifiedState
479
InitialisedState --(exit)-------> ExitedState
480
ModifiedState --(revert)-----> InitialisedState
481
ModifiedState --(modify)-----> ModifiedState
482
ModifiedState --(persist)----> InitialisedState
483
ModifiedState --(exit)-------> ExitedState
486
def __init__(self, proxy=None, proxy_loadargs=[], uisettings=None):
488
proxy = ConfigurationProxy(loadargs=proxy_loadargs)
489
self._current_state = VirginState(proxy, uisettings)
493
Expose the underlying L{ConfigurationState}, for testing purposes.
495
return self._current_state
497
def load_data(self, asynchronous=True, exit_method=None):
498
self._current_state = self._current_state.load_data(
499
asynchronous=asynchronous, exit_method=exit_method)
500
return isinstance(self._current_state, InitialisedState)
503
self._current_state = self._current_state.modify()
506
self._current_state = self._current_state.revert()
509
self._current_state = self._current_state.persist()
511
def _get_management_type(self):
512
return self._current_state.get(MANAGEMENT_TYPE)
514
def _set_management_type(self, value):
515
self._current_state.set(MANAGEMENT_TYPE, value)
517
management_type = property(_get_management_type, _set_management_type)
519
def _get_computer_title(self):
520
return self._current_state.get(COMPUTER_TITLE)
522
def _set_computer_title(self, value):
523
self._current_state.set(COMPUTER_TITLE, value)
525
computer_title = property(_get_computer_title, _set_computer_title)
527
def _get_hosted_landscape_host(self):
528
return self._current_state.get(HOSTED, LANDSCAPE_HOST)
530
hosted_landscape_host = property(_get_hosted_landscape_host)
532
def _get_local_landscape_host(self):
533
return self._current_state.get(LOCAL, LANDSCAPE_HOST)
535
def _set_local_landscape_host(self, value):
536
self._current_state.set(LOCAL, LANDSCAPE_HOST, value)
538
local_landscape_host = property(_get_local_landscape_host,
539
_set_local_landscape_host)
541
def _get_hosted_account_name(self):
542
return self._current_state.get(HOSTED, ACCOUNT_NAME)
544
def _set_hosted_account_name(self, value):
545
self._current_state.set(HOSTED, ACCOUNT_NAME, value)
547
hosted_account_name = property(_get_hosted_account_name,
548
_set_hosted_account_name)
550
def _get_local_account_name(self):
551
return self._current_state.get(LOCAL, ACCOUNT_NAME)
553
def _set_local_account_name(self, value):
554
self._current_state.set(LOCAL, ACCOUNT_NAME, value)
556
local_account_name = property(_get_local_account_name,
557
_set_local_account_name)
559
def _get_hosted_password(self):
560
return self._current_state.get(HOSTED, PASSWORD)
562
def _set_hosted_password(self, value):
563
self._current_state.set(HOSTED, PASSWORD, value)
565
hosted_password = property(_get_hosted_password,
566
_set_hosted_password)
568
def _get_local_password(self):
569
return self._current_state.get(LOCAL, PASSWORD)
571
def _set_local_password(self, value):
572
self._current_state.set(LOCAL, PASSWORD, value)
574
local_password = property(_get_local_password,
577
def _get_is_modified(self):
578
return isinstance(self.get_state(), ModifiedState)
580
is_modified = property(_get_is_modified)
582
def get_config_filename(self):
583
return self._current_state.get_config_filename()
585
def exit(self, asynchronous=True):
586
self._current_state.exit(asynchronous=asynchronous)