81d54a9d0c7b4bb0216beb4ef122ff1dc8950dd3
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
:
210 if self
.model
.applications
:
211 if application
in self
.model
.applications
:
212 app
= self
.model
.applications
[application
]
215 async def get_application_status(self
, application
, status
=None):
216 """Get the status of an application."""
217 if not self
.authenticated
:
221 app
= await self
.get_application(application
)
226 get_service_status
= get_application_status
228 async def get_config(self
, application
):
229 """Get the configuration of an application."""
230 if not self
.authenticated
:
234 app
= await self
.get_application(application
)
236 config
= await app
.get_config()
240 async def get_model(self
, name
='default'):
241 """Get a model from the Juju Controller.
243 Note: Model objects returned must call disconnected() before it goes
245 if not self
.authenticated
:
250 uuid
= await self
.get_model_uuid(name
)
262 async def get_model_uuid(self
, name
='default'):
263 """Get the UUID of a model.
265 Iterate through all models in a controller and find the matching model.
267 if not self
.authenticated
:
272 models
= await self
.controller
.get_models()
273 for model
in models
.user_models
:
274 if model
.model
.name
== name
:
275 uuid
= model
.model
.uuid
280 async def get_status(self
):
281 """Get the model status."""
282 if not self
.authenticated
:
286 self
.model
= self
.get_model(self
.model_name
)
293 status
= model_state()
294 status
.applications
= self
.model
.applications
295 status
.machines
= self
.model
.machines
299 async def is_application_active(self
, application
):
300 """Check if the application is in an active state."""
301 if not self
.authenticated
:
305 status
= await self
.get_application_status(application
)
306 if status
and status
in ['active']:
309 is_service_active
= is_application_active
311 async def is_application_blocked(self
, application
):
312 """Check if the application is in a blocked state."""
313 if not self
.authenticated
:
317 status
= await self
.get_application_status(application
)
318 if status
and status
in ['blocked']:
321 is_service_blocked
= is_application_blocked
323 async def is_application_deployed(self
, application
):
324 """Check if the application is in a deployed state."""
325 if not self
.authenticated
:
329 status
= await self
.get_application_status(application
)
330 if status
and status
in ['active']:
333 is_service_deployed
= is_application_deployed
335 async def is_application_error(self
, application
):
336 """Check if the application is in an error state."""
337 if not self
.authenticated
:
341 status
= await self
.get_application_status(application
)
342 if status
and status
in ['error']:
345 is_service_error
= is_application_error
347 async def is_application_maint(self
, application
):
348 """Check if the application is in a maintenance state."""
349 if not self
.authenticated
:
353 status
= await self
.get_application_status(application
)
354 if status
and status
in ['maintenance']:
357 is_service_maint
= is_application_maint
359 async def is_application_up(self
, application
=None):
360 """Check if the application is up."""
361 if not self
.authenticated
:
365 status
= await self
.get_application_status(application
)
366 if status
and status
in ['active', 'blocked']:
369 is_service_up
= is_application_up
371 async def login(self
):
372 """Login to the Juju controller."""
373 if self
.authenticated
:
376 self
.controller
= Controller()
379 await self
.controller
.connect(
386 await self
.controller
.connect_current()
388 self
.authenticated
= True
389 self
.model
= await self
.get_model(self
.model_name
)
391 async def logout(self
):
392 """Logout of the Juju controller."""
393 if not self
.authenticated
:
397 await self
.model
.disconnect()
400 await self
.controller
.disconnect()
401 self
.controller
= None
403 self
.authenticated
= False
405 async def remove_application(self
, name
):
406 """Remove the application."""
407 if not self
.authenticated
:
410 app
= await self
.get_application(name
)
414 async def resolve_error(self
, application
=None, status
=None):
415 """Resolve units in error state."""
416 if not self
.authenticated
:
419 app
= await self
.get_application(application
)
421 for unit
in app
.units
:
422 app
.resolved(retry
=True)
424 async def run_action(self
, application
, action_name
, **params
):
425 """Execute an action and return an Action object."""
426 if not self
.authenticated
:
435 app
= await self
.get_application(application
)
437 # We currently only have one unit per application
438 # so use the first unit available.
441 action
= await unit
.run_action(action_name
, **params
)
443 # Wait for the action to complete
446 result
['status'] = action
.status
447 result
['action']['tag'] = action
.data
['id']
448 result
['action']['results'] = action
.results
451 execute_action
= run_action
453 async def set_config(self
, application
, config
):
454 """Apply a configuration to the application."""
455 if not self
.authenticated
:
458 app
= await self
.get_application(application
)
460 await app
.set_config(config
)
462 async def set_parameter(self
, parameter
, value
, application
=None):
463 """Set a config parameter for a service."""
464 if not self
.authenticated
:
467 return await self
.apply_config(
469 application
=application
,
472 async def wait_for_application(self
, name
, timeout
=300):
473 """Wait for an application to become active."""
474 if not self
.authenticated
:
477 app
= await self
.get_application(name
)
479 await self
.model
.block_until(
481 unit
.agent_status
== 'idle'
482 and unit
.workload_status
483 in ['active', 'unknown'] for unit
in app
.units
490 parser
= argparse
.ArgumentParser(description
='Test Juju')
493 default
='10.0.202.49',
494 help="Juju controller"
499 help="User, default user-admin"
504 help="Password for the user"
509 help="Port number, default 17070"
513 help="Local directory for the charm"
521 help="IP of the VNF to configure"
526 help="The model to connect to."
528 return parser
.parse_args()
531 if __name__
== "__main__":
532 args
= get_argparser()
534 # Set logging level to debug so we can see verbose output from the
536 logging
.basicConfig(level
=logging
.DEBUG
)
538 # Quiet logging from the websocket library. If you want to see
539 # everything sent over the wire, set this to DEBUG.
540 ws_logger
= logging
.getLogger('websockets.protocol')
541 ws_logger
.setLevel(logging
.INFO
)
543 endpoint
= '%s:%d' % (args
.server
, int(args
.port
))
545 loop
= asyncio
.get_event_loop()
547 api
= JujuApi(server
=args
.server
,
550 secret
=args
.password
,
553 model_name
=args
.model
556 juju
.loop
.run(api
.login())
558 status
= juju
.loop
.run(api
.get_status())
560 print('Applications:', list(status
.applications
.keys()))
561 print('Machines:', list(status
.machines
.keys()))
563 if args
.directory
and args
.application
:
565 charm
= os
.path
.basename(args
.directory
)
567 api
.deploy_application(charm
,
568 name
=args
.application
,
573 juju
.loop
.run(api
.wait_for_application(charm
))
575 # Wait for the service to come up
576 up
= juju
.loop
.run(api
.is_application_up(charm
))
577 print("Application is {}".format("up" if up
else "down"))
579 print("Service {} is deployed".format(args
.application
))
581 ###########################
582 # Execute config on charm #
583 ###########################
584 config
= juju
.loop
.run(api
.get_config(args
.application
))
585 hostname
= config
['ssh-username']['value']
586 rhostname
= hostname
[::-1]
588 # Apply the configuration
589 juju
.loop
.run(api
.apply_config(
590 {'ssh-username': rhostname
}, application
=args
.application
593 # Get the configuration
594 config
= juju
.loop
.run(api
.get_config(args
.application
))
596 # Verify the configuration has been updated
597 assert(config
['ssh-username']['value'] == rhostname
)
599 ####################################
600 # Get the status of an application #
601 ####################################
602 status
= juju
.loop
.run(api
.get_application_status(charm
))
603 print("Application Status: {}".format(status
))
605 ###########################
606 # Execute a simple action #
607 ###########################
608 result
= juju
.loop
.run(api
.run_action(charm
, 'get-ssh-public-key'))
609 print("Action {} status is {} and returned {}".format(
611 result
['action']['tag'],
612 result
['action']['results']
615 #####################################
616 # Execute an action with parameters #
617 #####################################
618 result
= juju
.loop
.run(
619 api
.run_action(charm
, 'run', command
='hostname')
621 print("Action {} status is {} and returned {}".format(
623 result
['action']['tag'],
624 result
['action']['results']
627 juju
.loop
.run(api
.logout())
631 # if args.vnf_ip and \
632 # ('clearwater-aio' in args.directory):
633 # # Execute config on charm
634 # api._apply_config({'proxied_ip': args.vnf_ip})
636 # while not api._is_service_active():
639 # print ("Service {} is in status {}".
640 # format(args.service, api._get_service_status()))
642 # res = api._execute_action('create-update-user', {'number': '125252352525',
643 # 'password': 'asfsaf'})
645 # print ("Action 'creat-update-user response: {}".format(res))
647 # status = res['status']
648 # while status not in [ 'completed', 'failed' ]:
650 # status = api._get_action_status(res['action']['tag'])['status']
652 # print("Action status: {}".format(status))
654 # # This action will fail as the number is non-numeric
655 # res = api._execute_action('delete-user', {'number': '125252352525asf'})
657 # print ("Action 'delete-user response: {}".format(res))
659 # status = res['status']
660 # while status not in [ 'completed', 'failed' ]:
662 # status = api._get_action_status(res['action']['tag'])['status']
664 # print("Action status: {}".format(status))