40ff14c9c9b73cb364d61495fccc34190897ffcc
1 ############################################################################
2 # Copyright 2016 RIFT.io Inc #
4 # Licensed under the Apache License, Version 2.0 (the "License"); #
5 # you may not use this file except in compliance with the License. #
6 # You may obtain a copy of the License at #
8 # http://www.apache.org/licenses/LICENSE-2.0 #
10 # Unless required by applicable law or agreed to in writing, software #
11 # distributed under the License is distributed on an "AS IS" BASIS, #
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
13 # See the License for the specific language governing permissions and #
14 # limitations under the License. #
15 ############################################################################
24 from juju
.controller
import Controller
25 from juju
.model
import Model
, ModelObserver
28 ssl
._create
_default
_https
_context
= ssl
._create
_unverified
_context
29 except AttributeError:
30 # Legacy Python doesn't verify by default (see pep-0476)
31 # https://www.python.org/dev/peps/pep-0476/
35 class JujuVersionError(Exception):
39 class JujuApiError(Exception):
43 class JujuEnvError(JujuApiError
):
47 class JujuModelError(JujuApiError
):
51 class JujuStatusError(JujuApiError
):
55 class JujuUnitsError(JujuApiError
):
59 class JujuWaitUnitsError(JujuApiError
):
63 class JujuSrvNotDeployedError(JujuApiError
):
67 class JujuAddCharmError(JujuApiError
):
71 class JujuDeployError(JujuApiError
):
75 class JujuDestroyError(JujuApiError
):
79 class JujuResolveError(JujuApiError
):
83 class JujuActionError(JujuApiError
):
87 class JujuActionApiError(JujuActionError
):
91 class JujuActionInfoError(JujuActionError
):
95 class JujuActionExecError(JujuActionError
):
99 class JujuAuthenticationError(Exception):
103 class JujuMonitor(ModelObserver
):
104 """Monitor state changes within the Juju Model."""
105 # async def on_change(self, delta, old, new, model):
106 # """React to changes in the Juju model."""
108 # # TODO: Setup the hook to update the UI if the status of a unit changes
109 # # to be used when deploying a charm and waiting for it to be "ready"
110 # if delta.entity in ['application', 'unit'] and delta.type == "change":
113 # # TODO: Add a hook when an action is complete
118 class JujuApi(object):
119 """JujuApi wrapper on jujuclient library.
121 There should be one instance of JujuApi for each VNF manged by Juju.
124 Currently we use one unit per service/VNF. So once a service
125 is deployed, we store the unit name and reuse it
134 authenticated
= False
144 model_name
='default',
146 """Initialize with the Juju credentials."""
151 self
.log
= logging
.getLogger(__name__
)
153 self
.log
.debug('JujuApi instantiated')
159 if user
.startswith('user-'):
162 self
.user
= 'user-{}'.format(user
)
164 self
.endpoint
= '%s:%d' % (server
, int(port
))
166 self
.model_name
= model_name
172 """Close any open connections."""
175 async def apply_config(self
, config
, application
):
176 """Apply a configuration to the application."""
177 return await self
.set_config(application
=application
, config
=config
)
179 async def deploy_application(self
, charm
, name
="", path
=""):
180 """Deploy an application."""
181 if not self
.authenticated
:
184 app
= await self
.get_application(name
)
186 # TODO: Handle the error if the charm isn't found.
187 app
= await self
.model
.deploy(
189 application_name
=name
,
193 deploy_service
= deploy_application
195 async def get_action_status(self
, uuid
):
196 """Get the status of an action."""
197 if not self
.authenticated
:
200 action
= await self
.model
.wait_for_action(uuid
)
203 async def get_application(self
, application
):
204 """Get the deployed application."""
205 if not self
.authenticated
:
209 if application
in self
.model
.applications
:
210 app
= self
.model
.applications
[application
]
213 async def get_application_status(self
, application
, status
=None):
214 """Get the status of an application."""
215 if not self
.authenticated
:
219 app
= await self
.get_application(application
)
224 get_service_status
= get_application_status
226 async def get_config(self
, application
):
227 """Get the configuration of an application."""
228 if not self
.authenticated
:
232 app
= await self
.get_application(application
)
234 config
= await app
.get_config()
238 async def get_model(self
, name
='default'):
239 """Get a model from the Juju Controller.
241 Note: Model objects returned must call disconnected() before it goes
243 if not self
.authenticated
:
248 uuid
= await self
.get_model_uuid(name
)
260 async def get_model_uuid(self
, name
='default'):
261 """Get the UUID of a model.
263 Iterate through all models in a controller and find the matching model.
265 if not self
.authenticated
:
270 models
= await self
.controller
.get_models()
271 for model
in models
.user_models
:
272 if model
.model
.name
== name
:
273 uuid
= model
.model
.uuid
278 async def get_status(self
):
279 """Get the model status."""
280 if not self
.authenticated
:
284 self
.model
= self
.get_model(self
.model_name
)
291 status
= model_state()
292 status
.applications
= self
.model
.applications
293 status
.machines
= self
.model
.machines
297 async def is_application_active(self
, application
):
298 """Check if the application is in an active state."""
299 if not self
.authenticated
:
303 status
= await self
.get_application_status(application
)
304 if status
and status
in ['active']:
307 is_service_active
= is_application_active
309 async def is_application_blocked(self
, application
):
310 """Check if the application is in a blocked state."""
311 if not self
.authenticated
:
315 status
= await self
.get_application_status(application
)
316 if status
and status
in ['blocked']:
319 is_service_blocked
= is_application_blocked
321 async def is_application_deployed(self
, application
):
322 """Check if the application is in a deployed state."""
323 if not self
.authenticated
:
327 status
= await self
.get_application_status(application
)
328 if status
and status
in ['active']:
331 is_service_deployed
= is_application_deployed
333 async def is_application_error(self
, application
):
334 """Check if the application is in an error state."""
335 if not self
.authenticated
:
339 status
= await self
.get_application_status(application
)
340 if status
and status
in ['error']:
343 is_service_error
= is_application_error
345 async def is_application_maint(self
, application
):
346 """Check if the application is in a maintenance state."""
347 if not self
.authenticated
:
351 status
= await self
.get_application_status(application
)
352 if status
and status
in ['maintenance']:
355 is_service_maint
= is_application_maint
357 async def is_application_up(self
, application
=None):
358 """Check if the application is up."""
359 if not self
.authenticated
:
363 status
= await self
.get_application_status(application
)
364 if status
and status
in ['active', 'blocked']:
367 is_service_up
= is_application_up
369 async def login(self
):
370 """Login to the Juju controller."""
371 if self
.authenticated
:
374 self
.controller
= Controller()
377 await self
.controller
.connect(
384 await self
.controller
.connect_current()
386 self
.authenticated
= True
387 self
.model
= await self
.get_model(self
.model_name
)
389 async def logout(self
):
390 """Logout of the Juju controller."""
391 if not self
.authenticated
:
395 await self
.model
.disconnect()
398 await self
.controller
.disconnect()
399 self
.controller
= None
401 self
.authenticated
= False
403 async def remove_application(self
, name
):
404 """Remove the application."""
405 if not self
.authenticated
:
408 app
= await self
.get_application(name
)
412 async def resolve_error(self
, application
=None, status
=None):
413 """Resolve units in error state."""
414 if not self
.authenticated
:
417 app
= await self
.get_application(application
)
419 for unit
in app
.units
:
420 app
.resolved(retry
=True)
422 async def run_action(self
, application
, action_name
, **params
):
423 """Execute an action and return an Action object."""
424 if not self
.authenticated
:
433 app
= await self
.get_application(application
)
435 # We currently only have one unit per application
436 # so use the first unit available.
439 action
= await unit
.run_action(action_name
, **params
)
441 # Wait for the action to complete
444 result
['status'] = action
.status
445 result
['action']['tag'] = action
.data
['id']
446 result
['action']['results'] = action
.results
449 execute_action
= run_action
451 async def set_config(self
, application
, config
):
452 """Apply a configuration to the application."""
453 if not self
.authenticated
:
456 app
= await self
.get_application(application
)
458 await app
.set_config(config
)
460 async def set_parameter(self
, parameter
, value
, application
=None):
461 """Set a config parameter for a service."""
462 if not self
.authenticated
:
465 return await self
.apply_config(
467 application
=application
,
470 async def wait_for_application(self
, name
, timeout
=300):
471 """Wait for an application to become active."""
472 if not self
.authenticated
:
475 app
= await self
.get_application(name
)
477 await self
.model
.block_until(
479 unit
.agent_status
== 'idle'
480 and unit
.workload_status
481 in ['active', 'unknown'] for unit
in app
.units
488 parser
= argparse
.ArgumentParser(description
='Test Juju')
491 default
='10.0.202.49',
492 help="Juju controller"
497 help="User, default user-admin"
502 help="Password for the user"
507 help="Port number, default 17070"
511 help="Local directory for the charm"
519 help="IP of the VNF to configure"
524 help="The model to connect to."
526 return parser
.parse_args()
529 if __name__
== "__main__":
530 args
= get_argparser()
532 # Set logging level to debug so we can see verbose output from the
534 logging
.basicConfig(level
=logging
.DEBUG
)
536 # Quiet logging from the websocket library. If you want to see
537 # everything sent over the wire, set this to DEBUG.
538 ws_logger
= logging
.getLogger('websockets.protocol')
539 ws_logger
.setLevel(logging
.INFO
)
541 endpoint
= '%s:%d' % (args
.server
, int(args
.port
))
543 loop
= asyncio
.get_event_loop()
545 api
= JujuApi(server
=args
.server
,
548 secret
=args
.password
,
551 model_name
=args
.model
554 juju
.loop
.run(api
.login())
556 status
= juju
.loop
.run(api
.get_status())
558 print('Applications:', list(status
.applications
.keys()))
559 print('Machines:', list(status
.machines
.keys()))
561 if args
.directory
and args
.application
:
563 charm
= os
.path
.basename(args
.directory
)
565 api
.deploy_application(charm
,
566 name
=args
.application
,
571 juju
.loop
.run(api
.wait_for_application(charm
))
573 # Wait for the service to come up
574 up
= juju
.loop
.run(api
.is_application_up(charm
))
575 print("Application is {}".format("up" if up
else "down"))
577 print("Service {} is deployed".format(args
.application
))
579 ###########################
580 # Execute config on charm #
581 ###########################
582 config
= juju
.loop
.run(api
.get_config(args
.application
))
583 hostname
= config
['ssh-username']['value']
584 rhostname
= hostname
[::-1]
586 # Apply the configuration
587 juju
.loop
.run(api
.apply_config(
588 {'ssh-username': rhostname
}, application
=args
.application
591 # Get the configuration
592 config
= juju
.loop
.run(api
.get_config(args
.application
))
594 # Verify the configuration has been updated
595 assert(config
['ssh-username']['value'] == rhostname
)
597 ####################################
598 # Get the status of an application #
599 ####################################
600 status
= juju
.loop
.run(api
.get_application_status(charm
))
601 print("Application Status: {}".format(status
))
603 ###########################
604 # Execute a simple action #
605 ###########################
606 result
= juju
.loop
.run(api
.run_action(charm
, 'get-ssh-public-key'))
607 print("Action {} status is {} and returned {}".format(
609 result
['action']['tag'],
610 result
['action']['results']
613 #####################################
614 # Execute an action with parameters #
615 #####################################
616 result
= juju
.loop
.run(
617 api
.run_action(charm
, 'run', command
='hostname')
619 print("Action {} status is {} and returned {}".format(
621 result
['action']['tag'],
622 result
['action']['results']
625 juju
.loop
.run(api
.logout())
629 # if args.vnf_ip and \
630 # ('clearwater-aio' in args.directory):
631 # # Execute config on charm
632 # api._apply_config({'proxied_ip': args.vnf_ip})
634 # while not api._is_service_active():
637 # print ("Service {} is in status {}".
638 # format(args.service, api._get_service_status()))
640 # res = api._execute_action('create-update-user', {'number': '125252352525',
641 # 'password': 'asfsaf'})
643 # print ("Action 'creat-update-user response: {}".format(res))
645 # status = res['status']
646 # while status not in [ 'completed', 'failed' ]:
648 # status = api._get_action_status(res['action']['tag'])['status']
650 # print("Action status: {}".format(status))
652 # # This action will fail as the number is non-numeric
653 # res = api._execute_action('delete-user', {'number': '125252352525asf'})
655 # print ("Action 'delete-user response: {}".format(res))
657 # status = res['status']
658 # while status not in [ 'completed', 'failed' ]:
660 # status = api._get_action_status(res['action']['tag'])['status']
662 # print("Action status: {}".format(status))