Compare commits

...

612 Commits

Author SHA1 Message Date
PCoder 29bd6b5b3c Use correct app name 2021-04-15 09:45:20 +05:30
PCoder f02f15f09b First pass at the add-opennebula-vm-orders management command
Command executes but still does not create bills
2021-04-14 17:02:45 +05:30
Nico Schottelius df4c0c3060 in between commit to update for cc tests 2020-12-25 10:31:42 +01:00
Nico Schottelius 8dd4b712fb [views] add index view for uncloud 2020-12-25 10:11:13 +01:00
Nico Schottelius 50a395c8ec sort requirements.txt 2020-12-25 10:10:57 +01:00
Nico Schottelius 663d72269a [wireguard] verify key length 2020-12-25 10:08:34 +01:00
Nico Schottelius a0fbe2d6ed [wireguard] add unique constrain for keys in pool 2020-12-24 17:26:53 +01:00
Nico Schottelius 858aabb5ba Return value from validation 2020-12-20 22:03:43 +01:00
Nico Schottelius ece2bca831 add new /sizes endpoint 2020-12-20 21:45:47 +01:00
Nico Schottelius cdab685269 [vpn/doc] update docs 2020-12-20 19:37:12 +01:00
Nico Schottelius 689375a2fe Fix the config task 2020-12-20 19:17:03 +01:00
Nico Schottelius 8f83679c48 test cleaning tasks in a task fails:
[2020-12-20 18:01:50,264: WARNING/ForkPoolWorker-7] Pruning UncloudTask object (571ffc76-8b40-4cb6-9658-87030834bc6c)...
[2020-12-20 18:01:50,265: ERROR/ForkPoolWorker-7] Task uncloud.tasks.cleanup_tasks[f9fb1480-f122-41c9-bec1-3d6d0f92a22e] raised unexpected: RuntimeError('Never call result.get() within a task!\nSee http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks\n')
Traceback (most recent call last):
  File "/home/nico/vcs/uncloud/venv/lib/python3.8/site-packages/celery/app/trace.py", line 405, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/nico/vcs/uncloud/venv/lib/python3.8/site-packages/celery/app/trace.py", line 697, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/nico/vcs/uncloud/uncloud/tasks.py", line 13, in cleanup_tasks
    print(res.get())
  File "/home/nico/vcs/uncloud/venv/lib/python3.8/site-packages/celery/result.py", line 209, in get
    assert_will_not_block()
  File "/home/nico/vcs/uncloud/venv/lib/python3.8/site-packages/celery/result.py", line 37, in assert_will_not_block
    raise RuntimeError(E_WOULDBLOCK)
RuntimeError: Never call result.get() within a task!
See http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks
2020-12-20 19:01:37 +01:00
Nico Schottelius 5e870f04b1 ++celery/tasks 2020-12-20 18:36:46 +01:00
Nico Schottelius 63191c0a88 Remove $ that is not needed in python... 2020-12-20 13:24:55 +01:00
Nico Schottelius 03c0b34446 ++config vpn server 2020-12-20 13:00:36 +01:00
Nico Schottelius 1922a0d92d ++routing tests 2020-12-20 12:54:02 +01:00
Nico Schottelius 2e6c72c093 wireguard/celery fixes 2020-12-20 12:45:36 +01:00
Nico Schottelius b3626369a2 --syntax error 2020-12-20 12:24:35 +01:00
Nico Schottelius 179baee96d fix celery task routes syntax error 2020-12-20 12:22:50 +01:00
Nico Schottelius 054886fd9c begin phasing in config of vpn via cdist 2020-12-20 12:20:54 +01:00
Nico Schottelius e2b36c8bca celery test 2020-12-13 19:50:36 +01:00
Nico Schottelius 372fe800cd fill in template values for settings 2020-12-13 19:06:22 +01:00
Nico Schottelius 16f3adef93 [doc] ++requirements alpine 2020-12-13 18:56:47 +01:00
Nico Schottelius 2d62388eb1 phasing in celery
for configuring the vpn server
2020-12-13 18:34:43 +01:00
Nico Schottelius aec79cba74 [vpn] include vpn server public key 2020-12-13 18:05:48 +01:00
Nico Schottelius cd19c47fdb [vpn] implement creating vpns 2020-12-13 17:59:35 +01:00
Nico Schottelius cf948b03a8 ++vpn network 2020-12-13 13:28:43 +01:00
Nico Schottelius 5716cae900 [vpn] add selector for size 2020-12-13 11:43:49 +01:00
Nico Schottelius 10d5a72c5a [refactor] cleaning up uncloud_net for Wireguardvpn 2020-12-13 11:38:41 +01:00
Nico Schottelius 074cffcbd7 Add selection for vpnnetworkreservations 2020-12-09 21:20:33 +01:00
Nico Schottelius 7f32d05cd4 begin phasing in vpn support [poc] 2020-12-09 20:22:33 +01:00
Nico Schottelius 0fd5ac18cd do not import pay->auth
Try to keep common things in the "uncloud" module
2020-12-06 11:53:37 +01:00
Nico Schottelius ad0c2f1e9d Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-11-17 11:47:53 +01:00
Nico Schottelius 0b1c2cc168 Cleanup code so that *most* test work again
Still need to solve the downgrade test
2020-11-15 15:43:11 +01:00
ahmadbilalkhalid 4845ab1e39 Create account using api
Registration and change_email is backed by ldap
2020-11-14 14:50:43 +05:00
Nico Schottelius ecc9e6f734 [reverseDNS] add basic logic 2020-10-25 22:43:34 +01:00
Nico Schottelius 20c7c86703 restructure to move uncloudnetwork into core 2020-10-25 21:00:30 +01:00
Nico Schottelius 8959bc6ad5 various updates 2020-10-25 13:52:36 +01:00
Nico Schottelius 0cd8a3a787 ++update ungleich_provider 2020-10-11 22:36:01 +02:00
Nico Schottelius bbc7625550 phase in configuration - move address to base 2020-10-11 22:32:08 +02:00
Nico Schottelius fe4e200dc0 Begin phasing in the uncloudprovider 2020-10-11 17:45:25 +02:00
Nico Schottelius e03cdf214a update VAT importer 2020-10-08 19:54:04 +02:00
Nico Schottelius 50fd9e1f37 ++work 2020-10-07 00:54:56 +02:00
Nico Schottelius 2e74661702 Fix first test case / billing 2020-10-06 23:14:32 +02:00
Nico Schottelius c26ff253de One step furter to allow saving of orders w/o explicit recurringperiod 2020-10-06 19:21:37 +02:00
Nico Schottelius 9623a77907 Updating for products/recurring periods 2020-10-06 18:53:13 +02:00
Nico Schottelius c435639241 gitignore some tests 2020-10-06 16:13:03 +02:00
Nico Schottelius 992c7c551e Make recurring period a database model
- For easier handling (foreignkeys, many2many)
- For higher flexibility (users can define their own periods)
2020-10-06 15:46:22 +02:00
Nico Schottelius 58883765d7 [tests] back to 5 working tests! 2020-09-28 23:16:17 +02:00
Nico Schottelius 8d8c4d660c Can order a generic product now 2020-09-28 21:59:35 +02:00
Nico Schottelius c32499199a Add JSON support for product description 2020-09-28 21:34:24 +02:00
Nico Schottelius c6bacab35a Phasing out Product model
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-09-28 20:59:08 +02:00
Nico Schottelius 1aead50170 remove big mistake: orders from product
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-09-28 20:44:50 +02:00
Nico Schottelius d8a7964fed Continue to refactor for shifting logic into the order 2020-09-09 00:35:55 +02:00
Nico Schottelius 077c665c53 ++update 2020-09-03 17:16:18 +02:00
Nico Schottelius f7274fe967 Adding logic to order to find out whether its closed 2020-09-03 16:38:51 +02:00
Nico Schottelius 1c7d81762d begin splitting bill record creation function 2020-09-02 16:02:28 +02:00
Nico Schottelius 18f9a3848a Implement ending/replacing date logic 2020-08-27 22:00:54 +02:00
Nico Schottelius 9211894b23 implement basic logic for updating a recurring order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-27 14:45:37 +02:00
Nico Schottelius b8b15704a3 begin testing bill sums
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-25 21:53:25 +02:00
Nico Schottelius ab412cb877 Test that creating products w/o correct billing address fails 2020-08-25 21:31:12 +02:00
Nico Schottelius 7b83efe995 [pay] make sample products more modular 2020-08-25 21:11:28 +02:00
Nico Schottelius 4d5ca58b2a [tests] cleanup old tests
Finally manage.py tests runs through
2020-08-25 20:40:33 +02:00
Nico Schottelius f693dd3d18 ++notes 2020-08-09 21:10:43 +02:00
Nico Schottelius 5ceaaf7c90 bill cleanup, note next step 2020-08-09 14:52:42 +02:00
Nico Schottelius 2b29e300dd [product] migrate orders to ManyToManyField 2020-08-09 14:44:29 +02:00
Nico Schottelius 8df1d8dc7c begin refactor product to user orders instead of single order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 14:38:10 +02:00
Nico Schottelius ef02cb61fd Refine tests for bills, multiple bills 2020-08-09 12:34:25 +02:00
Nico Schottelius 70c450afc8 fix tests for Product()
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 11:44:22 +02:00
Nico Schottelius 0dd1093812 add sample products and improve testing for Product 2020-08-09 11:02:45 +02:00
Nico Schottelius 6a928a2b2a Fix tests for billing 2020-08-09 10:18:15 +02:00
Nico Schottelius 89519e48a9 Various updates 2020-08-09 10:14:49 +02:00
Nico Schottelius e169b8c1d1 Implement the whole billing logic
The major part has been written!
2020-08-09 10:14:31 +02:00
Nico Schottelius d7c0c40926 first bill generation works
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 00:37:27 +02:00
Nico Schottelius fd39526350 Improve billing address testing 2020-08-08 23:02:24 +02:00
Nico Schottelius 78d1de9031 Remove orderrecord
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:37:00 +02:00
Nico Schottelius db1a69561b pass first 2 bill tests
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:31:43 +02:00
Nico Schottelius 9bf0a99f6a uncloud pay cleanups
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:20:49 +02:00
Nico Schottelius c9be8cc50b orders only have 1 price
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 21:54:44 +02:00
Nico Schottelius 8da6a1e19c update models/new django
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 21:54:07 +02:00
Nico Schottelius ff8fdb76b1 ++ test 2020-08-08 19:30:25 +02:00
Nico Schottelius 9b00ef11fb ++stuff 2020-08-04 18:56:36 +02:00
Nico Schottelius d2bd6ba200 [test] update for uncloud_net 2020-08-04 12:41:15 +02:00
Nico Schottelius 165dacb7bf update to use new JSONFIELD
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 12:01:44 +02:00
Nico Schottelius ee79877a27 +migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 11:40:34 +02:00
Nico Schottelius 2ce667e8c7 model changes
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 11:26:42 +02:00
Nico Schottelius e563780142 Add some sample VMs 2020-08-02 00:55:07 +02:00
Nico Schottelius 66233a1ce5 [doc] more cleanup 2020-08-01 23:44:10 +02:00
Nico Schottelius 9d5d8657cb [doc] Move install and co. into the main documentation 2020-08-01 23:42:36 +02:00
Nico Schottelius a091079677 in between commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 23:20:14 +02:00
Nico Schottelius c9a941e290 [vm] add disks inlined to VMs 2020-08-01 18:48:51 +02:00
Nico Schottelius f7b14bf507 cleanup migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 18:38:38 +02:00
Nico Schottelius ed40b21d16 [doc] begin describe replacing orders 2020-08-01 18:31:27 +02:00
Nico Schottelius 880e4d046b [vm] cleanup 2020-08-01 18:30:40 +02:00
Nico Schottelius cbd5a08ae7 make price a real property of order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 18:27:12 +02:00
Nico Schottelius 64780bfc6c [models] update / doc 2020-08-01 16:29:24 +02:00
Nico Schottelius 9c1b4ab275 [db] add migration for vmdiskproduct 2020-08-01 16:29:11 +02:00
Nico Schottelius 2771a7518a [doc] cleanup 2020-08-01 16:28:19 +02:00
Nico Schottelius 9b3493a661 [doc] integrate vpn into manual 2020-08-01 16:24:21 +02:00
Nico Schottelius 932ac06cea begin doc in views 2020-08-01 14:05:56 +02:00
Nico Schottelius 05a897db70 +whitespace 2020-08-01 14:05:50 +02:00
Nico Schottelius 011096f152 [bootstrap user] remove syntax errors 2020-08-01 14:05:36 +02:00
Nico Schottelius 55ba61e36b [urls] remove obsolete comment 2020-08-01 14:05:26 +02:00
Nico Schottelius 1265e23750 [doc] add bootstrap 2020-08-01 14:05:12 +02:00
Nico Schottelius bc0c77a393 add sql migration for opennebula
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 14:03:22 +02:00
Nico Schottelius bdba3bffe2 [opennebula] turn VM into a product 2020-08-01 14:02:54 +02:00
Nico Schottelius 3745a0e2b1 [doc] ignore generated files 2020-08-01 13:24:51 +02:00
Nico Schottelius 7ce28b0b60 Begin new uncloud manual 2020-08-01 13:07:44 +02:00
Nico Schottelius 662e706eab begin to fix bill view 2020-06-21 23:54:57 +02:00
Nico Schottelius 11de455d23 begin to add pdf view of bill into admin 2020-06-21 23:46:26 +02:00
Nico Schottelius 126d9da764 Use quantity instead of usage_count 2020-06-21 16:42:55 +02:00
Nico Schottelius 8a17ee6de5 Include BillRecords in the admin 2020-06-21 16:08:00 +02:00
Nico Schottelius 721472b416 Fix constraint to active = True 2020-06-21 14:45:05 +02:00
Nico Schottelius 424ceafbb8 Enhance make-admin to allow making superuser 2020-06-21 14:44:55 +02:00
Nico Schottelius 1e68539ed8 remove uuid primary key
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-06-21 14:35:12 +02:00
Nico Schottelius 3ef19610f3 add script to reset migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-06-21 14:34:48 +02:00
Nico Schottelius 8decfe1b16 Phase in admin, remove uuid from bills 2020-06-21 13:46:54 +02:00
Nico Schottelius 662845128f ++ billing details 2020-06-20 23:47:26 +02:00
Nico Schottelius 95011c2058 Add readme for billing 2020-06-20 22:15:15 +02:00
Nico Schottelius a3f3ca8cf9 in the middle of restructering 2020-05-24 13:45:03 +02:00
Nico Schottelius 5d1eaaf0af Add new models backup - before major refactoring 2020-05-24 12:46:11 +02:00
Nico Schottelius bcd141730d convert recurring period into an integerfield
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-23 23:38:34 +02:00
Nico Schottelius 15535433e8 begin to change to day based differences
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-23 23:32:45 +02:00
Nico Schottelius 18b862c2e1 fix syntax errors 2020-05-23 23:08:59 +02:00
Nico Schottelius b8652c921e Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-05-23 21:33:04 +02:00
Nico Schottelius 8bbcc5df5f ++dev 2020-05-23 21:32:56 +02:00
Nico Schottelius 0202f80a37 in between pay commit 2020-05-23 21:23:06 +02:00
Nico Schottelius caedf874e4 [vpn] add tests 2020-05-20 21:00:08 +02:00
Nico Schottelius f17f9060b0 [config] add path to chrome 2020-05-17 23:34:13 +02:00
Nico Schottelius 04fac71a85 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-17 22:38:49 +02:00
Nico Schottelius ef76304bae + old stuff 2020-05-17 22:35:25 +02:00
Nico Schottelius ec447e0dc4 Add support for primary address in user.
Closes #35

Fixes #35
2020-05-10 21:47:44 +02:00
Nico Schottelius ca2065a94d gitignore for local settings 2020-05-10 14:37:20 +02:00
Nico Schottelius dc7a465a8c Fix exception if first order does not have billing address 2020-05-10 14:22:09 +02:00
fnux 65440ab2ef Add depends_on relation on orders 2020-05-08 16:47:32 +02:00
fnux d794b24c86 Make VM order-able again 2020-05-08 16:31:33 +02:00
fnux df059fb00d Speed-up CI with pre-built image, add resources directory 2020-05-08 12:15:40 +02:00
fnux 67af7b5465 Add missing billnico migration 2020-05-08 11:43:01 +02:00
fnux beb5bd7ee4 Fix existing uncloud_pay tests 2020-05-08 11:33:59 +02:00
fnux 9574d69f4c Move VAT rate CSV out of archive/ 2020-05-08 11:23:09 +02:00
fnux 74e2168529 Fix floating-point issue on bills (Fix #31) 2020-05-08 11:13:11 +02:00
fnux 444d6ded28 Move AMOUNT_* and COUNTRIES to uncloud_pay/init.py 2020-05-08 10:56:03 +02:00
fnux cbba1f4169 Add admin bill generation endpoint 2020-05-08 10:42:04 +02:00
fnux d47c94ba84 Implement non-destructive order updates 2020-05-08 10:07:44 +02:00
fnux 89e853b490 Add order termination logic 2020-05-08 09:31:46 +02:00
Nico Schottelius 1b97fc8fc7 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-05-07 20:22:49 +02:00
Nico Schottelius 41a2f18453 In between commit 2020-05-07 20:22:42 +02:00
fnux a086d46136 Allow bill download from admin bill endpoint 2020-05-07 15:45:04 +02:00
fnux ae2bad5754 Generate bill PDFs from /my/bill 2020-05-07 15:38:49 +02:00
fnux 3874165189 Fix bill generation 2020-05-07 14:24:04 +02:00
fnux 56d98cbb55 Implement Orders/Bills permissions, unpaid bill views 2020-05-07 13:12:38 +02:00
fnux 718abab9d2 Add make-admin command to uncloud_auth 2020-05-07 12:45:06 +02:00
fnux 268e08c4db Adapt README for SQLite 2020-05-07 12:31:59 +02:00
fnux b8ac99acb6 On more small commit to fix README formatting 2020-05-07 12:25:05 +02:00
fnux 221d98af4b Inline CI badges 2020-05-07 12:24:17 +02:00
fnux ebd4e6fa1b Add fancy CI badges to README 2020-05-07 12:23:17 +02:00
fnux b512d42058 Add devel environment setup instructions 2020-05-07 12:21:49 +02:00
fnux 1245c191c0 Adapt CI to new structure 2020-05-07 12:13:48 +02:00
fnux 95d43f002f Move django-based uncloud to top-level 2020-05-07 12:12:35 +02:00
fnux 0560063326 Add description field to Orders 2020-05-07 12:08:18 +02:00
fnux db3c29d17e Fix admin order creation 2020-05-07 12:05:26 +02:00
fnux 892b2b6f13 Revert "Disable vat validator to get project back running"
This reverts commit 1cf20a2cb6.
2020-05-07 12:03:28 +02:00
Nico Schottelius aa8ade4730 Add readme about identifiers 2020-05-05 16:01:47 +02:00
Nico Schottelius 594f1a9b69 +hacks 2020-05-05 15:19:50 +02:00
Nico Schottelius e3b28354fe ++notes 2020-05-05 15:19:04 +02:00
Nico Schottelius 99a18232aa VMs now properly set their pricing 2020-05-02 23:44:20 +02:00
Nico Schottelius c835c874d5 [BREAKING] make Order a stand-alone version
I think that while the idea of an Orderrecord is good, we might get
away / have a simpler implementation if we only use orders and
reference them where needed.

I saved the previous Order model for easy rollback, if my assumption
is wrong.
2020-05-02 22:48:05 +02:00
Nico Schottelius 028f1ebe6e Entry point /beta/vm/ works for creating VM + order 2020-05-02 22:03:34 +02:00
Nico Schottelius 9ef5309b91 +db migrations for pay/vm
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-02 21:21:29 +02:00
Nico Schottelius 4097c2ce13 BillingAddress: make mget_preferred_address a classmethod 2020-05-02 21:20:14 +02:00
Nico Schottelius 736fe27493 Add issues.org as a shortcut for registering issues 2020-05-02 20:45:19 +02:00
Nico Schottelius 7d708cfbb6 Fix empty vat number bug 2020-05-02 20:42:09 +02:00
Nico Schottelius 927fb20671 Versionise API and cleanups 2020-05-02 20:31:36 +02:00
Nico Schottelius eea654a9f8 Phase in new beta/vm view for creating vms + orders + bills 2020-05-02 19:15:48 +02:00
Nico Schottelius 2cda6441f4 Refactor secret / local settings handling 2020-05-02 00:16:29 +02:00
Nico Schottelius 62d9ccbbef [vpn] begin to introduce save() method
The save() and delete() method will create/manage the orders
2020-04-27 18:25:44 +02:00
Nico Schottelius 1cf20a2cb6 Disable vat validator to get project back running 2020-04-27 18:25:27 +02:00
fnux 94932edebe Add user admin endpoint, import from LDAP 2020-04-18 15:11:52 +02:00
fnux a15952862a Make VM order-able again 2020-04-18 15:11:46 +02:00
fnux cec4263621 Merge branch 'vat-handling' into 'master'
VAT support handling

See merge request uncloud/uncloud!7
2020-04-18 11:57:16 +02:00
fnux f61b91dab2 Catch any exception from VIES VAT check 2020-04-18 11:51:13 +02:00
fnux b3afad5d5d Compute VAT rate and amount on bill generation 2020-04-18 11:43:55 +02:00
fnux 3a03717b12 Split bills between orders of the same billing address 2020-04-18 11:21:11 +02:00
fnux db9ff5d18b Display allr elevant values on Bill serializer/page 2020-04-18 10:43:23 +02:00
fnux a49fe6ff51 Properly wire billing addresses to uncloud_service 2020-04-18 10:40:11 +02:00
fnux dd0c1cba94 Remove legacy ungleich_service migrations 2020-04-18 10:39:57 +02:00
fnux c0e12884e1 Sync migrations - again! 2020-04-18 09:38:12 +02:00
fnux 9bbe3b3b56 Adapt uncloud_pay tests to support billing addresses 2020-04-18 09:26:34 +02:00
fnux e6eba7542b Minor fixes, DB sync after rebase 2020-04-18 09:26:21 +02:00
fnux 0522927c50 Start wiring BillingAddresses to bills & orders 2020-04-18 09:13:52 +02:00
fnux 3fa1d5753e Minimal VAT validation on billing address registration 2020-04-18 09:13:04 +02:00
fnux c6ca94800e Add BillingAddress structure to users 2020-04-18 09:13:04 +02:00
fnux ad187c02da Import VAT rates "importer" from dynamicweb 2020-04-18 09:13:04 +02:00
fnux 7afb3f8793 Merge branch 'uncloud-product-activation' into 'master'
Handle product activation on payment

See merge request uncloud/uncloud!6
2020-04-18 09:08:32 +02:00
fnux 86775af4c8 Fix product activation tests after rebase 2020-04-18 09:02:33 +02:00
fnux 83d2cd465d Sync migrations after rebase 2020-04-18 08:42:50 +02:00
fnux b6c976b722 Commit autp-generated migrations (missing from master?) 2020-04-18 08:36:41 +02:00
fnux d1e993140c Add simple product activation test 2020-04-18 08:36:41 +02:00
fnux c57780fb4d Add naive GenericServiceProduct 2020-04-18 08:36:41 +02:00
fnux 83a0ca0e4e Adapt billing tests to product activation structure 2020-04-18 08:35:22 +02:00
fnux 5d5bf486b5 Initial product activation implementation 2020-04-18 08:34:41 +02:00
fnux 839bd5f8a9 Merge branch 'vm-ordering-disk' into 'master'
Wire disk images to VM creation/ordering, make Order records transparents

See merge request uncloud/uncloud!4
2020-04-18 08:32:07 +02:00
fnux aa0702faba Add chromium to path on CI environment 2020-04-13 12:06:03 +02:00
fnux 9a57153c4e Commit missing migrations 2020-04-13 12:02:49 +02:00
fnux f5897ed4b1 Adapt recurring price of VM and Matrix to new scheme 2020-04-13 12:00:59 +02:00
fnux 14f59430bb Restore Order.add_record, used by uncloud_pay tests 2020-04-13 11:54:41 +02:00
fnux 05f8f1f6c0 Fix dependency issue in CI job 2020-04-13 11:40:19 +02:00
fnux 1a58508f8b Rename ungleich_service into uncloud_service 2020-04-13 11:39:49 +02:00
fnux e67bd03997 Migration fix after rebase 2020-04-13 11:19:18 +02:00
fnux a4cc4304f9 Adapt managed service to create VMDiskProduct 2020-04-13 11:19:18 +02:00
fnux d3b7470294 Wire disk images to VM creation/ordering 2020-04-13 11:19:18 +02:00
fnux a7e9f3c09d Move Order.add_record to save hook in abstract Product 2020-04-13 10:42:44 +02:00
Nico Schottelius ff133e81b7 [vpn] update to show reservations, create wireguard config 2020-04-12 22:55:22 +02:00
Nico Schottelius 85b4d70592 [vpn] make a vpn creat-able!
[15:40] line:~% http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch)  http://localhost:8000/net/vpn/ network_size=48  wireguard_public_key=$(wg genkey | wg pubkey)
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 206
Content-Type: application/json
Date: Sun, 12 Apr 2020 13:40:26 GMT
Server: WSGIServer/0.2 CPython/3.7.3
Vary: Accept
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "extra_data": null,
    "network": "2a0a:e5c1:203::",
    "order": null,
    "owner": 30,
    "status": "PENDING",
    "uuid": "8f977a8f-e06a-4346-94ae-8f525df58b7b",
    "wireguard_public_key": "JvCuUTZHm9unasJkGsLKN0Bf/hu6ZSIv7dnIGPyJ6xA="
}
2020-04-12 15:40:39 +02:00
Nico Schottelius b55254b9b1 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-12 14:28:39 +02:00
Nico Schottelius bc033a9087 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-12 11:36:51 +02:00
Nico Schottelius 05f38d157e add a discounter function to the product model 2020-04-12 11:35:37 +02:00
Nico Schottelius bab59b1879 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-11 21:37:50 +02:00
Nico Schottelius 50b8b7a5f6 in-between commit 2020-04-11 21:37:36 +02:00
nico14571 f1bba63f6f Merge branch 'yearly-billing' into 'master'
Implement yearly billing, general billing tests

See merge request uncloud/uncloud!5
2020-04-11 21:37:13 +02:00
fnux 276c7e9901 Set VM order starting date on creation 2020-04-09 14:52:56 +02:00
fnux 3a37343a73 Set default value for vpnnetworkreservation, rebuild migrations 2020-04-09 14:28:46 +02:00
fnux 3588ae88f9 Merge branch 'master' into HEAD 2020-04-09 14:10:01 +02:00
Nico Schottelius 9431f11284 ++notes 2020-04-09 12:09:38 +02:00
Nico Schottelius e64c2b8ddb Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-09 12:08:17 +02:00
Nico Schottelius d9473e8f33 ++ doc 2020-04-09 12:08:11 +02:00
fnux cb3346303b Fix typo in migration dependencies for uncloud_pay 2020-04-09 12:06:05 +02:00
Nico Schottelius 7d892daff9 [db] stay on psql+socket 2020-04-09 11:59:49 +02:00
Nico Schottelius 08b9886ce3 Remove sample secret key in secrets_sample
No need to worry, this was just a testing key
2020-04-09 11:59:25 +02:00
nico14571 d6bdf5c991 Merge branch 'sync-old-meowpay-patches' into 'master'
Revamped payment pipeline (imported from nico/mew-cloud)

See merge request uncloud/uncloud!3
2020-04-09 11:31:49 +02:00
fnux cc7056c87c Remove old Stripe settings from secrets_sample.py 2020-04-08 17:55:48 +02:00
fnux a8b81b074b Remove user view from uncloud_pay 2020-04-08 17:40:44 +02:00
fnux 89c705f7d2 Set one payment method as primary, allow updates 2020-04-08 17:22:53 +02:00
fnux f2a797874a Merge branch 'master' into stripe-js 2020-04-08 17:08:09 +02:00
Nico Schottelius d3f2a3e071 in between commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-08 16:24:39 +02:00
Nico Schottelius 3d2f8574d3 [db] use tcp -> support ssh 2020-04-08 13:09:17 +02:00
Nico Schottelius 1838eaf7dd Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-08 12:07:11 +02:00
Nico Schottelius 8986835c7e Add readme for postgresql support 2020-04-08 12:03:18 +02:00
Nico Schottelius 938f0a3390 update to work on different computer
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-07 19:45:16 +02:00
Nico Schottelius 913e992a48 [vpn] fix urls 2020-04-06 22:30:01 +02:00
Nico Schottelius 096f7e05c0 [migration] new models for uncloud_net 2020-04-06 22:29:41 +02:00
Nico Schottelius 06c4a5643c [doc] move readme to subdir 2020-04-06 22:08:29 +02:00
Nico Schottelius 198aaea48a Remove unused ldaptest 2020-04-06 22:06:48 +02:00
Nico Schottelius d537e9e2f0 [doc] add new readme's 2020-04-06 22:06:34 +02:00
Nico Schottelius 5d084a5716 phase in vpn
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-03 19:27:49 +02:00
Nico Schottelius 8fb3ad7fe8 inline all pictures 2020-04-03 18:51:09 +02:00
Nico Schottelius c44faa7a73 Begin to include bill output 2020-04-03 18:41:17 +02:00
Nico Schottelius fa0ca2d9c1 Merge remote-tracking branch 'meowpaylocal/master' 2020-04-02 19:31:57 +02:00
Nico Schottelius 7a6c8739f6 Rename / prepare for merge with uncloud repo 2020-04-02 19:31:03 +02:00
Nico Schottelius 833d570472 sync .gitignore 2020-04-02 19:30:47 +02:00
Nico Schottelius 3cf3439f1c Move all files to _etc_based 2020-04-02 19:29:08 +02:00
Nico Schottelius 23203ff418 vmsnapshot progress 2020-03-22 20:55:11 +01:00
Nico Schottelius 9961ca0446 add new migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-22 18:59:59 +01:00
Nico Schottelius 105142f76a Add template for creating VMs 2020-03-22 18:52:31 +01:00
Nico Schottelius 08fe3e689e Add debug to opennebula, create VM disks from opennebula correctly 2020-03-22 17:30:55 +01:00
Nico Schottelius 10c5257f90 Introduce "extra_data" jsonfield 2020-03-21 11:59:04 +01:00
Nico Schottelius a32f7522b5 Relate VM to disks and snapshots 2020-03-18 15:43:01 +01:00
Nico Schottelius 4b4cbbf009 Also list snapshots for a VM 2020-03-18 15:19:06 +01:00
Nico Schottelius c6a9bd4363 Make balance a user attribute + decimalfield 2020-03-18 14:53:26 +01:00
Nico Schottelius cd01f62fde Move user view to uncloud_auth 2020-03-18 14:36:40 +01:00
Nico Schottelius 2f1aee8181 Can create a VMSnapshot w/ order (bugs to be removed) 2020-03-17 19:53:14 +01:00
Nico Schottelius 6a382fab23 [vmhost] add used_ram_in_gb 2020-03-17 19:07:00 +01:00
Nico Schottelius b9473c1803 ++ fix opennebula migration 2020-03-17 16:03:41 +01:00
Nico Schottelius cc2efa5c14 Remove old opennebula view, remove vmid field 2020-03-17 15:40:08 +01:00
Nico Schottelius 5d840de55c [opennebula] refresh formula, cleanup vm import/migration to uncloud 2020-03-17 15:39:24 +01:00
Nico Schottelius 9f4b927c74 Introduce mirations to ungleich_service to make tests work 2020-03-17 14:50:28 +01:00
Nico Schottelius 55bd42fe64 List all VMs for admins 2020-03-17 14:50:14 +01:00
Nico Schottelius 8634d667d5 update requirements for graphing 2020-03-17 14:49:59 +01:00
Nico Schottelius ac7ea86668 rename opennebula commands 2020-03-17 14:49:49 +01:00
Nico Schottelius 8356404fe4 ++ product readme 2020-03-17 14:49:36 +01:00
Nico Schottelius 723d2a99cc add django…extensions to support "graph_models" 2020-03-17 13:30:48 +01:00
Nico Schottelius 8f4e7cca1b add migrations to ungleich_service so tests don't fail
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-17 12:46:02 +01:00
ahmadbilalkhalid b15a12dc71 Missing import for DCLVMProductSerializer 2020-03-13 14:22:49 +05:00
fnux 923102af24 Fix DCLVMProductSerializer import following rebase 2020-03-09 13:17:40 +01:00
fnux f4ebbb79ce Add test coverage parsing to CI 2020-03-09 12:25:12 +01:00
fnux 122bc586b4 Disable 'old' broken/not-yet-implemented ungleich_vm tests
To make CI happy on merge requests from now on :-)
2020-03-09 12:25:12 +01:00
fnux ae6548e168 Read database host and user from environment (used by CI pipeline) 2020-03-09 12:25:12 +01:00
fnux 0e4068cea8 Add minimal CI running django tests 2020-03-09 12:25:12 +01:00
fnux 623d3ae5c4 Fix various billing issues discovered by testing 2020-03-09 12:25:12 +01:00
fnux fe0e6d98bf Add simple tests for billing 2020-03-09 12:25:11 +01:00
fnux 41e35c1af0 Add missing migration and dependency to run tests 2020-03-09 12:25:11 +01:00
fnux 948391ab2e Dump test placeholder for uncloud_pay 2020-03-09 12:25:11 +01:00
fnux d089d06264 Initial yearly billing implementation 2020-03-09 12:25:11 +01:00
fnux c086dbd357 Rebuild paymentmethod/stripe migrations from master 2020-03-09 12:25:11 +01:00
fnux 545727afe7 Move STRIPE_PUBLIC_KEY to secrets (i.e. local configuration) 2020-03-09 12:25:11 +01:00
fnux 7bbc729b87 Fix duplicates in payment method creation 2020-03-09 12:25:11 +01:00
fnux b10cae472e Fix migration dependencies after rebase 2020-03-09 12:25:11 +01:00
fnux 952cf8fd13 Remove unused empty migration 2020-03-09 12:25:11 +01:00
fnux 7e278228bd Fix payment update updates 2020-03-09 12:25:11 +01:00
fnux 31507c0f1a Fix error in stripe get_customer_id_for 2020-03-09 12:25:11 +01:00
fnux 7e58a8ace2 Fix generate-bills, remove debug print in charge method 2020-03-09 12:25:11 +01:00
fnux a4fa0def4b Fix dumb logic errors/typo from last commit 2020-03-09 12:25:11 +01:00
fnux 80fe28588e Revamp stripe error handling 2020-03-09 12:25:11 +01:00
fnux 4e658d2d77 Remove legacy credit card support 2020-03-09 12:25:11 +01:00
fnux 5161a74354 Add STRIPE_PUBLIC_KEY setting 2020-03-09 12:25:11 +01:00
fnux 0e62ccff3b Cleanup/reorder uncloud_pay views 2020-03-09 12:25:11 +01:00
fnux bf83b750de Replace legacy Stripe Charge API by Payment{setup, intent} 2020-03-09 12:24:14 +01:00
Nico Schottelius 47148454f6 s/_/-/ for bill id 2020-03-06 11:11:16 +01:00
Nico Schottelius 0032c272e7 Merge branch 'bill-id' of code.ungleich.ch:nico/meow-pay 2020-03-06 11:10:47 +01:00
Nico Schottelius 263125048d Begin to introduce a DCL alike view for VMs 2020-03-06 11:10:20 +01:00
fnux 658262c599 Add human readable reference to bills 2020-03-06 09:39:41 +01:00
fnux c41b55573a Fix order link in BillRecordSerializer 2020-03-06 09:32:25 +01:00
fnux 4016c28c5f Fix generate-bills 2020-03-06 09:17:31 +01:00
Nico Schottelius aa8336b7e4 VM: def __str__ 2020-03-05 23:55:33 +01:00
Nico Schottelius 0e6a6afd88 [opennebula] Fix fields/serializers 2020-03-05 23:18:07 +01:00
Nico Schottelius efbe1c0596 Merge commands into the "vm" command 2020-03-05 17:52:01 +01:00
fnux 6c7f0e98b3 Rebuild paymentmethod/stripe migrations from master 2020-03-05 16:24:45 +01:00
Nico Schottelius b8c2f80e45 [vmhost] add available_ram_in_gb and available_cores 2020-03-05 15:06:34 +01:00
Nico Schottelius 139aca6a61 Remove vms field from vmhost
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 14:58:45 +01:00
Nico Schottelius 2a73f0e767 [migration] make vm name optional, use storage class choices
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 14:22:56 +01:00
Nico Schottelius 66e224e926 [storage] move choices to uncloud_storage 2020-03-05 14:21:10 +01:00
Nico Schottelius 10f09c7115 add an old client hack (just for reference) 2020-03-05 14:15:33 +01:00
Nico Schottelius ec7a2a3c3a Correct pricing for VMProduct 2020-03-05 14:00:14 +01:00
fnux b07df26eb2 Move STRIPE_PUBLIC_KEY to secrets (i.e. local configuration) 2020-03-05 11:51:08 +01:00
fnux b958cc77ea Fix duplicates in payment method creation 2020-03-05 11:45:37 +01:00
fnux e9b6a6f277 Fix migration dependencies after rebase 2020-03-05 11:43:07 +01:00
fnux 546667d117 Remove unused empty migration 2020-03-05 11:39:48 +01:00
fnux b88dfa4bfe Fix payment update updates 2020-03-05 11:39:48 +01:00
fnux d6ee806467 Fix error in stripe get_customer_id_for 2020-03-05 11:39:48 +01:00
fnux a41184d83d Fix generate-bills, remove debug print in charge method 2020-03-05 11:39:48 +01:00
fnux 2f70418f4d Fix dumb logic errors/typo from last commit 2020-03-05 11:39:48 +01:00
fnux 21e1a3d220 Revamp stripe error handling 2020-03-05 11:39:48 +01:00
fnux 4cc19e1e6e Remove legacy credit card support 2020-03-05 11:39:48 +01:00
fnux 08bf7cd320 Add STRIPE_PUBLIC_KEY setting 2020-03-05 11:39:48 +01:00
fnux 7e9f2ea561 Cleanup/reorder uncloud_pay views 2020-03-05 11:39:48 +01:00
fnux 929211162d Replace legacy Stripe Charge API by Payment{setup, intent} 2020-03-05 11:39:48 +01:00
Nico Schottelius cf17373b3f Fix ahmed introduced migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 11:35:00 +01:00
Nico Schottelius 14a4fa8cc1 Merge remote-tracking branch 'ahmed/migrate-one-to-regular-vm' 2020-03-05 11:34:15 +01:00
Nico Schottelius 4fc1c36ae9 fix incorrect migrations from fnux-stable branch
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 11:17:30 +01:00
fnux 371c5ccf00 Merge remote-tracking branch 'origin/fnux-hacks' 2020-03-04 12:12:22 +01:00
fnux faca104459 Fix stripe import in uncloud_pay.models 2020-03-04 11:05:21 +01:00
fnux 9e8149135b Move bill generation logic to Bill class, initial work for prepaid 2020-03-04 10:55:12 +01:00
ahmadbilalkhalid 02b287eff8 small cleaning 2020-03-04 14:44:41 +05:00
fnux 9aabc66e57 Pay: move some model-related methods from helpers to models
Otherwise we end up in circular dependency hell
2020-03-04 09:39:18 +01:00
ahmadbilalkhalid a662b1fe29 Make migrate-one-vm-to-regular command idempotent 2020-03-04 13:25:46 +05:00
ahmadbilalkhalid 88c10e2e4a improve readability 2020-03-03 23:53:45 +05:00
ahmadbilalkhalid fea0568bb9 init commit 2020-03-03 23:46:39 +05:00
fnux e0cb6ac670 Allow for charging customers 2020-03-03 18:16:25 +01:00
Nico Schottelius ebc9238845 recreate all migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-03 17:50:52 +01:00
fnux 94a39ed81d Properly wire stripe card to payment methods 2020-03-03 16:56:42 +01:00
fnux 5c2d2a5b94 Document relations for Orders and Managed Services 2020-03-03 13:14:51 +01:00
Nico Schottelius e9ef2acb06 Add readme for objects 2020-03-03 12:15:05 +01:00
fnux 3846e49395 Fix migration issue introduced in previous merge 2020-03-03 11:40:37 +01:00
fnux a849e642dd Merge remote-tracking branch 'origin/master' into fnux-hacks 2020-03-03 11:36:08 +01:00
fnux 28407bf3e3 Quickly document OrderRecord class 2020-03-03 11:34:47 +01:00
Nico Schottelius ea00e81b1e Move all stripe stuff to stripe.py 2020-03-03 11:31:32 +01:00
fnux 53baf0d9f3 Fix typo in BillRecord 2020-03-03 11:29:57 +01:00
fnux 11e22f5001 Consistently use one_time_price instead of setup_fee 2020-03-03 11:27:35 +01:00
Nico Schottelius e176ad0817 Remove second stripe key definition 2020-03-03 11:26:16 +01:00
Nico Schottelius a50095f873 Merge remote-tracking branch 'origin/fnux-stable'
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-03 11:23:29 +01:00
fnux a40da40169 Add recurring_count to bills 2020-03-03 11:15:48 +01:00
fnux 2eaaad49db Handle setup fee in bills 2020-03-03 10:59:21 +01:00
fnux 9fdf66ed74 Fix MatrixService ordering 2020-03-03 10:51:16 +01:00
fnux b31aa72f84 Allow to select billing period when registering VM 2020-03-03 10:14:56 +01:00
fnux 5559d600c7 Move things around for readability in uncloud_pay models and serializer 2020-03-03 09:13:04 +01:00
fnux 4e51670a90 Expand recurring period billing logic for DD/MM/hh/month 2020-03-03 08:53:19 +01:00
fnux 4ad737ed90 Initial stripe playground 2020-03-02 22:29:50 +01:00
fnux c651c4ddaa Cleanup a bit BillRecord 2020-03-02 16:41:49 +01:00
ahmadbilalkhalid 531bfa1768 actual thing name is replaced by pseudo names 2020-03-02 19:20:12 +05:00
ahmadbilalkhalid 750d8c8cbf Use fictional hostname for VMHost 2020-03-02 17:42:54 +05:00
ahmadbilalkhalid 0c3e6d10ae Indentation/Spacing fixes 2020-03-02 17:20:30 +05:00
ahmadbilalkhalid afdba3d7d9 Remove duplicate code 2020-03-02 17:17:30 +05:00
ahmadbilalkhalid 52e74c22cc Merge branch 'nico/meow-pay-master' into HEAD 2020-03-02 17:05:23 +05:00
ahmadbilalkhalid 6c9c63e0da Add sample clean() for model + Add tests for uncloud_vm 2020-03-02 16:54:36 +05:00
fnux 9e9018060e Wire order records to bills, fix user balance 2020-03-02 10:46:04 +01:00
fnux 9e253d497b Wrap VM creation in database transaction 2020-03-02 09:30:51 +01:00
fnux 81bd54116a Add records to orders 2020-03-02 09:25:03 +01:00
fnux 8e41b894c0 Add OrderRecord model 2020-03-02 08:09:42 +01:00
Ahmed Bilal 3228b91038 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!7
2020-03-02 07:17:04 +01:00
Ahmed Bilal 028fd6789f ++cleanup
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-02 07:17:04 +01:00
fnux 4f25086a63 Only generate bill if no overlap 2020-03-01 15:47:27 +01:00
fnux be2b0a8855 Fix a few errors on preview billing rework
Another WIP commit to sync with laptop, do not forget to rebase!
2020-03-01 12:23:04 +01:00
Nico Schottelius 4115eed2a8 +migration
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:58:10 +01:00
Nico Schottelius 5c33bc5c02 support creating disks
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:57:57 +01:00
Nico Schottelius 6a38e4e0a4 add url for importing disk image
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:00:13 +01:00
Nico Schottelius bcbd6f6f83 Introduce disk->image relationship
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 16:45:52 +01:00
fnux e319d1d151 WIP revamped bill logic 2020-02-29 09:08:55 +01:00
fnux af1265003e Define custom fields and serializer for MatrixServiceProduct 2020-02-28 16:26:45 +01:00
fnux eaa483e018 Commit forgottem uncloud_vm migrations 2020-02-28 15:08:45 +01:00
fnux 181005ad6c Cleanup VMProduct serializer, add name field to VMProduct 2020-02-28 15:08:00 +01:00
fnux b3bbfafa04 Introduce custom ProductViewSet preventing customer from updating
products
2020-02-28 14:57:45 +01:00
fnux 33cc2b2111 Add uncloud_storage template app 2020-02-28 14:48:01 +01:00
fnux 3b87a47430 Add initial ungleich_service app with MatrixServiceProduct shell 2020-02-28 14:46:33 +01:00
fnux b5a242f176 Merge branch 'master' into fnux-hacks 2020-02-28 14:06:29 +01:00
fnux 1cb1de4876 Add (broken) charge method to payment method endpoint 2020-02-28 11:10:31 +01:00
fnux c0512e54b0 Add handle-overdue-bills 2020-02-28 10:18:24 +01:00
fnux e12575e1de Commit forgotten migration on Orders (Float->Decimal) 2020-02-28 09:59:13 +01:00
fnux adb57c55ca Revamp generate-bills logic to avoid overlapping 2020-02-28 09:58:01 +01:00
Nico Schottelius 89215e47b6 phase in mac 2020-02-28 09:34:29 +01:00
fnux 37ed126bc1 Create payment on strip charging 2020-02-28 09:26:18 +01:00
fnux 4bed53c8a8 Wire charge-negative-balance to payment methods 2020-02-28 09:10:36 +01:00
fnux 059791e2f2 Add initial generate-bills and charge-negative-balance uncloud-pay
commands
2020-02-28 08:59:32 +01:00
fnux ef5e7e8035 Quickly wire vm creation to orders 2020-02-28 07:26:34 +01:00
fnux b1649a6228 Remove product resolution from /order endpoint 2020-02-28 07:25:56 +01:00
fnux 0e28e50bac Revert "Commit WIP changes for /order, if needed at any point"
This reverts commit 83794a1781a1b84506100b39a6997882c654b4f3.
2020-02-28 07:25:18 +01:00
fnux 38d3a3a5d3 Commit WIP changes for /order, if needed at any point 2020-02-28 07:25:18 +01:00
fnux 809a55e1dd Wire VMProduct creation to order 2020-02-28 07:25:18 +01:00
fnux b2fe5014d8 Make recurring_period an Enum, VMProduct a Product, initial wire for
order
2020-02-28 07:25:05 +01:00
fnux 1dd3324275 Wiring initial user balance 2020-02-28 07:24:51 +01:00
fnux 36fcff5149 Add initial structure for payment methods 2020-02-28 07:24:51 +01:00
Nico Schottelius 288a65f219 ++update
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 15:29:15 +01:00
Nico Schottelius bd6008462d add template for uncloud_net
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 15:29:05 +01:00
Nico Schottelius 70a4fe4d90 order: serialize all fields 2020-02-27 12:45:54 +01:00
Nico Schottelius e89fb45f9c Merge remote-tracking branch 'origin/fnux-hacks' 2020-02-27 12:43:24 +01:00
fnux 1ff5702ce3 Expose Order model 2020-02-27 12:42:24 +01:00
Nico Schottelius b722f30ea5 ++doc 2020-02-27 12:42:09 +01:00
fnux f5eadd6ddb Move user view to uncloud_pay 2020-02-27 12:38:04 +01:00
Nico Schottelius 1445acb77a Merge remote-tracking branch 'origin/fnux-hacks' 2020-02-27 12:36:40 +01:00
Nico Schottelius 7bf4f2adb2 --debug
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:36:33 +01:00
Nico Schottelius a9aac39486 Create a vmsnapshot + associated order 2020-02-27 12:31:20 +01:00
fnux 225f20c91b Fix typo in payment source model 2020-02-27 12:21:52 +01:00
fnux b9b605f407 Add ADMIN endpoints for bills and payments 2020-02-27 12:21:25 +01:00
fnux f358acca05 Fix payment creation 2020-02-27 12:11:13 +01:00
Nico Schottelius 41a5eae879 cleanup views/vmsnapshot 2020-02-27 12:09:29 +01:00
Nico Schottelius 657dfc541e Merge remote-tracking branch 'origin/fnux-hacks'
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:06:02 +01:00
Nico Schottelius fd648ade65 ++cleanup
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:02:41 +01:00
fnux 06ab21c577 Fix python errors on latest hack commits => make runserver happy again 2020-02-27 11:59:28 +01:00
Nico Schottelius 11d629bb51 [uncloud_api] completely remove it 2020-02-27 11:42:42 +01:00
Nico Schottelius aa59b05a2d cleanup urls 2020-02-27 11:40:36 +01:00
Nico Schottelius a58a361254 Move snapshot to _pay and _vm 2020-02-27 11:36:50 +01:00
Nico Schottelius 1ca247148c [uncloud_pay] add "prototype" 2020-02-27 11:21:38 +01:00
ahmadbilalkhalid 033b1e846a Merge branch 'nico/meow-pay-master' into HEAD 2020-02-27 14:35:52 +05:00
Nico Schottelius c0bf4d96c4 ++ debian/devuan notes 2020-02-26 21:13:30 +01:00
Ahmed Bilal 0a6fa031d3 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!5
2020-02-26 11:31:18 +01:00
Ahmed Bilal 0c7ca1147a fix migrations the ugly way
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-26 11:31:17 +01:00
Nico Schottelius df851bee08 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-26 11:16:46 +01:00
Nico Schottelius 0b60765e2b in between commit 2020-02-26 11:16:42 +01:00
Nico Schottelius bd3d21faa9 add thoughts for health checking 2020-02-25 22:04:04 +01:00
Nico Schottelius c7ded96658 vmhosts, restructure urls, etc. 2020-02-25 22:01:55 +01:00
Nico Schottelius d4b170f813 phase in vmhost
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 20:53:12 +01:00
Nico Schottelius 446c13b77c fix/simplify syncvm 2020-02-25 19:23:39 +01:00
Nico Schottelius defe36bfb6 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-25 18:28:19 +01:00
Nico Schottelius e790063d5a Merge remote-tracking branch 'ahmed/master' into ahmed_merge
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 18:19:55 +01:00
Nico Schottelius cc3d2f2d42 in-between-commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 18:15:22 +01:00
ahmadbilalkhalid d658b9635d Replace (vmid,uuid) with id in VM model + Add last_host and graphics in VM model + Fixed retrieve view in uncloud.opennebula 2020-02-25 21:03:20 +05:00
Nico Schottelius 7d1c8df84d ++ postgres requirement 2020-02-25 14:20:03 +01:00
Nico Schottelius cc9e5905eb update
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 14:12:23 +01:00
ahmadbilalkhalid c7252cde53 Introduced local settings in meow-pay/uncloud django app 2020-02-25 13:09:54 +05:00
ahmadbilalkhalid a72bc142a6 Fixed issues in opennebula/views.py + syncvm now behaves correctly and print users which are not in ldap as per their email address 2020-02-25 11:50:49 +05:00
ahmadbilalkhalid 739bd72526 Migration fixed + opennebula/views.py fixed 2020-02-23 23:00:42 +05:00
Nico Schottelius 15b0fe3dc9 fix migrations the ugly way
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-23 18:11:14 +01:00
Nico Schottelius 50df7050d6 vmhost: add status field 2020-02-23 17:46:30 +01:00
Nico Schottelius 734c406245 Extend uncloud VM models 2020-02-23 17:43:06 +01:00
ahmadbilalkhalid b3e505d37c migration fix 2020-02-23 21:34:22 +05:00
Ahmed Bilal 98f84f1cf5 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!4
2020-02-23 17:23:57 +01:00
Nico Schottelius 8c6e4eee00 -- merge conflict 2020-02-23 17:20:28 +01:00
Nico Schottelius 2900844f63 Merge remote-tracking branch 'ahmed/master' 2020-02-23 17:17:08 +01:00
Nico Schottelius 46921c43ad update ldap, update syncvm 2020-02-23 17:11:05 +01:00
ahmadbilalkhalid fa4d7a1d70 opennebula_hacks added i.e create one user and chown of vm 2020-02-23 21:00:18 +05:00
Nico Schottelius edbfb7964e [ldap] bind with admin to get attributes 2020-02-23 16:52:30 +01:00
ahmadbilalkhalid e4f2f446f5 Merge branch 'nico/meow-pay-master' into HEAD 2020-02-23 20:25:35 +05:00
Nico Schottelius e2b5b5d102 opennebula -> router 2020-02-23 15:33:26 +01:00
ahmadbilalkhalid 7b09f0a373 abk-hacks added 2020-02-23 19:18:51 +05:00
Nico Schottelius cee45b5227 -typo 2020-02-23 15:09:58 +01:00
Nico Schottelius 94633d6cc8 move uncloud a layer up
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-23 14:07:37 +01:00
Nico Schottelius 1d1ae6fb3e Force uniqueness on uuid 2020-02-23 11:59:09 +01:00
Nico Schottelius f8c29aa1d6 add uuid() to opennebula VM 2020-02-23 11:55:57 +01:00
Nico Schottelius fc4ec7b0f8 update readme + api 2020-02-23 11:42:15 +01:00
Nico Schottelius f588691f0d [opennebula] add json, add helper functions 2020-02-23 11:42:03 +01:00
Nico Schottelius 581865460b Mess with migrations 2020-02-23 11:41:51 +01:00
Nico Schottelius 7f821b4d5a add readme 2020-02-23 10:31:28 +01:00
Nico Schottelius ce0da4b827 + bracket 2020-02-23 09:44:55 +01:00
Nico Schottelius de06b9ee22 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-23 09:18:27 +01:00
Nico Schottelius 26449d3159 ++snapshot ideas 2020-02-23 09:18:16 +01:00
ahmadbilalkhalid 71a764ce1e Move vm/{detail,list} under opennebula/vm/{detail,list} and make it admin accessible only + Created vm/list that list currently authenticated user's VMs 2020-02-22 15:49:00 +05:00
ahmadbilalkhalid 5f28e9630c Remove unneccessary requirements from {repo_root}/requirements.txt + uncloud/secret_sample.py minor changes 2020-02-22 11:36:18 +05:00
Ahmed Bilal 2aa22803f4 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!2
2020-02-22 07:32:52 +01:00
Ahmed Bilal dc34c0ecd4 Merge nico/meow-pay into ahmedbilal/meow-pay 2020-02-22 07:32:52 +01:00
Nico Schottelius 4f4a4be839 good night commit - introducing status 2020-02-22 00:50:06 +01:00
Nico Schottelius b1bb6bc314 Make products available via getattr 2020-02-22 00:22:42 +01:00
Nico Schottelius b67f41cc35 Merge remote-tracking branch 'ahmed/master' 2020-02-21 22:04:26 +01:00
Nico Schottelius dc5092be71 Add sample secrets 2020-02-21 21:41:51 +01:00
Nico Schottelius 4df7c761d3 ++stuff 2020-02-21 20:51:04 +01:00
ahmadbilalkhalid d61a7e670f opennebula vm sync/query application added 2020-02-21 20:33:37 +05:00
ahmadbilalkhalid 5de973b204 Merge branch 'nico/meow-pay-master' into HEAD 2020-02-21 20:06:21 +05:00
Nico Schottelius 0708a1e1fd add requirements.txt 2020-02-21 15:05:17 +01:00
Nico Schottelius a5695ffa48 two more related user problems
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 11:43:17 +01:00
Nico Schottelius 6ba224638a fix migrations / custom user late introduce
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 11:42:54 +01:00
Nico Schottelius 2cda4dd57b [auth] add customer user model
Best practice

See

https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project
2020-02-21 11:32:41 +01:00
Nico Schottelius c456355059 begin to introduce product
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 10:41:22 +01:00
Nico Schottelius 118c66799c ++views/permissions 2020-02-20 19:38:43 +01:00
Nico Schottelius f8182e00e8 import secrets 2020-02-20 19:38:30 +01:00
Nico Schottelius 9fd445e947 add ldap support + tutorial example 2020-02-20 18:58:07 +01:00
Nico Schottelius 254429db55 .gitignore & more 2020-02-20 16:55:01 +01:00
Nico Schottelius e472d20ae0 hacking uncloud v202002
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-20 16:52:50 +01:00
Nico Schottelius 315aaded41 Focus on creating a VPN as a first test case 2020-02-20 16:05:58 +01:00
Nico Schottelius 13292db39e +old notes 2020-02-20 11:57:03 +01:00
Nico Schottelius 0a1ccadda2 +ldaptest 2020-02-20 11:56:47 +01:00
Nico Schottelius 8160d01471 Merge remote-tracking branch 'origin/master' 2020-02-20 11:51:14 +01:00
ahmadbilalkhalid 8c353f277c is_order_valid added in helper.py 2020-02-20 15:23:15 +05:00
ahmadbilalkhalid bb18f6b0e9 Flask-RESTful added in requirements.txt 2020-02-20 14:08:39 +05:00
ahmadbilalkhalid 00b35e0567 cleaned requirements.txt 2020-02-20 14:04:53 +05:00
ahmadbilalkhalid 9c7d458eec use code from ungleich-common 2020-02-20 13:57:32 +05:00
Nico Schottelius 074efffaa7 ++ hack 2020-02-20 09:44:30 +01:00
ahmadbilalkhalid cee92f2e99 A lot of code moved to ungleich-common 2020-02-20 00:12:11 +05:00
ahmadbilalkhalid ce709c3b6f Add certificates option for etcd 2020-02-19 14:44:19 +05:00
ahmadbilalkhalid 519279ce6f Update README.md 2020-02-19 13:13:39 +05:00
ahmadbilalkhalid 7b9a970307 Update README.md 2020-02-19 13:12:46 +05:00
ahmadbilalkhalid 5f1f451bc2 Added installation and getting started instructions in README.md 2020-02-19 13:12:07 +05:00
ahmadbilalkhalid e37592bdc6 README.md updated and reorganized, Improved error handling for configparser and ldap manager, requirements.txt added 2020-02-19 11:59:54 +05:00
ahmadbilalkhalid 347843cb24 Sample config file added + uncloud dependency removed 2020-02-19 10:22:15 +05:00
Nico Schottelius aa9548e753 +gitignore
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-15 11:15:26 +01:00
Nico Schottelius c1f384fb9a so many notes&hacks! 2020-02-15 09:38:33 +01:00
Nico Schottelius b38c9b6060 Ad capability to add and list hosts 2020-02-09 19:27:24 +01:00
Nico Schottelius a80a279ba5 Add filtering support:
(venv) [12:54] diamond:uncloud% ./bin/uncloud-run-reinstall hack --product 'dualstack-vm' --os-image-name alpine311 --username nicocustomer --password '...' --hackprefix ~/vcs/uncloud/uncloud/hack/hackcloud/ --etcd-host etcd1.ungleich.ch --etcd-ca-cert ~/vcs/ungleich-dot-cdist/files/etcd/ca.pem --etcd-cert-cert ~/vcs/ungleich-dot-cdist/files/etcd/nico.pem --etcd-cert-key ~/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem --list-orders --filter-order-key "status" --filter-order-regexp NEW
2020-02-09 12:54:52 +01:00
Nico Schottelius 5ef009cc9b Begin to phase in features and processing orders 2020-02-09 12:12:15 +01:00
Nico Schottelius 5da6dbb32e ++hack / list products
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-09 11:14:50 +01:00
Nico Schottelius 3b508fc87d phase in notion of a product 2020-02-09 09:36:50 +01:00
Nico Schottelius 55a2de72c8 [hack] begin to add ldap authentication 2020-02-09 08:51:35 +01:00
Nico Schottelius f99d0a0b64 [requirements] add ldap3 2020-02-09 08:43:56 +01:00
Nico Schottelius d9a756b50e Catch filenotfound errors when launching etcd 2020-02-06 15:33:01 +01:00
Nico Schottelius 592b745cea exit if an exception happened 2020-02-06 15:32:48 +01:00
Nico Schottelius aaf0114df1 add image format option 2020-02-06 15:13:08 +01:00
fnux 0e667b5262 Fix UUID variable in oneshot/vm/get_name 2020-01-30 09:00:28 +01:00
fnux f2337a14eb Yet another forgotten CLI parameter in oneshot... 2020-01-30 08:55:56 +01:00
fnux 8797e93baf Fix --name support in oneshot 2020-01-30 08:54:58 +01:00
fnux 9e2751c41e Remove deplicate vm definition in oneshot --stop 2020-01-30 08:52:24 +01:00
fnux 17d0c61407 Fix --accel parameter for oneshot 2020-01-30 08:47:23 +01:00
Nico Schottelius 3171ab8ccb [hack/vm] add self.vm dict 2020-01-29 19:55:55 +01:00
Nico Schottelius 56565ac7f7 Fix AttributeError: 'VM' object has no attribute 'vm'
ERROR:uncloud.vmm:Error occurred while starting VM.
Detail qemu-system-x86_64: -drive file=/home/nico/vcs/uncloud/uncloud/hack/hackcloud/alpine-virt-3.11.2-x86_64.iso,format=qcow2,if=virtio: Image is not in qcow2 format
Traceback (most recent call last):
  File "/home/nico/vcs/uncloud/uncloud/vmm/__init__.py", line 186, in start
    sp.check_output(command, stderr=sp.PIPE)
  File "/usr/lib/python3.8/subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/usr/lib/python3.8/subprocess.py", line 512, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['sudo', '-p', 'Enter password to start VM 87230168-1b74-49f7-97c3-c968a26fc65e: ', '/usr/bin/qemu-system-x86_64', '-name', 'uncoud-87230168-1b74-49f7-97c3-c968a26fc65e', '-machine', 'pc,accel=kvm', '-drive', 'file=/home/nico/vcs/uncloud/uncloud/hack/hackcloud/alpine-virt-3.11.2-x86_64.iso,format=qcow2,if=virtio', '-device', 'virtio-rng-pci', '-m', '1024M', '-smp', 'cores=1,threads=1', '-netdev', 'tap,id=netmain,script=/home/nico/vcs/uncloud/uncloud/hack/hackcloud/ifup.sh,downscript=/home/nico/vcs/uncloud/uncloud/hack/hackcloud/ifdown.sh,ifname=uc000000000000', '-device', 'virtio-net-pci,netdev=netmain,id=net0,mac=42:00:00:00:00:01', '-qmp', 'unix:/home/nico/vcs/uncloud/uncloud/hack/hackcloud/sock/87230168-1b74-49f7-97c3-c968a26fc65e,server,nowait', '-vnc', 'unix:/tmp/tmpep71nz1f', '-daemonize']' returned non-zero exit status 1.
ERROR:root:'VM' object has no attribute 'vm'
Traceback (most recent call last):
  File "./bin/../scripts/uncloud", line 82, in <module>
    main(arguments)
  File "/home/nico/vcs/uncloud/uncloud/hack/main.py", line 47, in main
    vm.create()
  File "/home/nico/vcs/uncloud/uncloud/hack/vm.py", line 115, in create
    self.vm['mac'] = self.mac
AttributeError: 'VM' object has no attribute 'vm'
(venv) [18:49] diamond:uncloud% ./bin/uncloud-run-reinstall hack --create-vm --hackprefix ~/vcs/uncloud/uncloud/hack/hackcloud/ --image alpine-virt-3.11.2-x86_64.iso --no-db
2020-01-29 19:30:19 +01:00
Nico Schottelius 1b08a49aef Do not background dnsmasq 2020-01-29 18:45:50 +01:00
Dominique Roux 7e36b0c067 Debugging pipeline 2020-01-29 17:25:29 +01:00
Dominique Roux 1ca2f8670d Wrote first unit tests 2020-01-29 17:15:34 +01:00
Dominique Roux d8a465bca4 Changed Exception in MAC class 2020-01-29 17:06:54 +01:00
Dominique Roux dfa4e16806 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-29 17:06:14 +01:00
Dominique Roux bdbf26cfd4 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-29 17:06:11 +01:00
Dominique Roux 5969d3b13d accessed the mac class with the correct function 2020-01-29 17:04:59 +01:00
fnux 3e69fb275f Oneshot: cleanup CLI, initial networking support 2020-01-29 10:08:15 +01:00
fnux 618fecb73f Initial implementation (no networking) of uncloud-oneshot 2020-01-28 16:30:43 +01:00
fnux e2cd44826b Fix typo in hack/vm.py 2020-01-28 13:45:20 +01:00
fnux 1758629ca1 Add minimal doc to hack/vm.py 2020-01-28 12:33:36 +01:00
fnux a759b8aa39 VMM: make use of socket_dir 2020-01-28 12:24:26 +01:00
fnux 4c6a126d8b Hack/VM: wire get_vnc and list_vms 2020-01-28 11:02:18 +01:00
fnux 2b71c1807d Wire uncloud-hack vm module to VMM 2020-01-28 09:25:25 +01:00
ahmadbilalkhalid 200a7672f2 make value_in_json=True 2020-01-27 14:55:26 +05:00
ahmadbilalkhalid 1a76d2b5f3 Many more changes 2020-01-27 13:40:57 +05:00
fnux cbcaf63650 Update VM images documentation (upstream images, uncloud-init) 2020-01-26 12:04:37 +01:00
Dominique Roux 5d05e91335 added hackerprefix argument, changed the commandline structure of vm to work better with sudo 2020-01-24 17:12:50 +01:00
Dominique Roux 8cc58726d0 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-24 14:34:36 +01:00
Dominique Roux 5711bf4770 bugfixes in vm 2020-01-24 14:34:34 +01:00
Nico Schottelius ae3482cc71 Fix and break some VM stuff 2020-01-24 14:21:38 +01:00
Nico Schottelius b1319d654a Make me and Dominique happy (aka add vxlan to bridge) 2020-01-24 14:15:48 +01:00
Nico Schottelius 93d7a409b1 Fix Dominique's sudo bug
Totally not related to my previous commit
2020-01-24 14:10:49 +01:00
Nico Schottelius 7e91f60c0a sudo fix 2020-01-24 14:10:08 +01:00
Dominique Roux 58daf8191e refactored vm.py to create a VM 2020-01-24 13:56:08 +01:00
Nico Schottelius b5409552d8 prepare vm.py for dominique 2020-01-23 21:20:16 +01:00
Dominique Roux 550937630c Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-23 21:17:30 +01:00
Dominique Roux 46a04048b5 small changes in vm.py to make it more generic 2020-01-23 21:17:09 +01:00
Nico Schottelius 3ddd27a08d Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-23 21:16:10 +01:00
Nico Schottelius c881c7ce4d hack mac: be a proper python class 2020-01-23 21:15:26 +01:00
Dominique Roux d5a7f8ef59 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-23 18:43:42 +01:00
Dominique Roux 0982927c1b Added DNSmasq ability for RA 2020-01-23 18:43:41 +01:00
Nico Schottelius 8e839aeb44 commit stuff before dominique does 2020-01-23 18:41:59 +01:00
ahmadbilalkhalid da54a59ca2 initial commit 2020-01-20 12:30:12 +05:00
Nico Schottelius 8888f5d9f7 add logging 2020-01-19 12:55:06 +01:00
Nico Schottelius bd9dbb12b7 Cleanup networking 2020-01-19 11:30:41 +01:00
Nico Schottelius 30be791312 Be less verbose when reinstalling 2020-01-19 11:30:30 +01:00
Nico Schottelius 2b8831784a [pep440] improve versioning name for python 2020-01-19 11:30:16 +01:00
Nico Schottelius b847260768 ++network 2020-01-19 09:16:29 +01:00
Nico Schottelius 1b5a3f6d2e Progress with networking 2020-01-15 13:26:05 +01:00
Nico Schottelius 8a451ff4ff [hack] phase in networking 2020-01-15 12:40:37 +01:00
Nico Schottelius bd03f95e99 [docs] move one level higher 2020-01-15 11:32:23 +01:00
Nico Schottelius 26d5c91625 Update hacking docs 2020-01-15 10:53:22 +01:00
Nico Schottelius b877ab13b3 add hack code 2020-01-15 10:02:37 +01:00
Nico Schottelius 12e8ccd01c Cleanups for mac handling 2020-01-14 19:10:59 +01:00
Nico Schottelius 8078ffae5a Add working --last-used-mac
{'create_vm': False, 'last_used_mac': True, 'get_new_mac': False, 'debug': False, 'conf_dir': '/home/nico/uncloud', 'etcd_host': 'etcd1.ungleich.ch', 'etcd_port': None, 'etcd_ca_cert': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/ca.pem', 'etcd_cert_cert': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/nico.pem', 'etcd_cert_key': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem'}
00:20:00:00:00:00
(venv) [19:02] diamond:uncloud% ./bin/uncloud-run-reinstall hack  --etcd-host etcd1.ungleich.ch --etcd-ca-cert /home/nico/vcs/ungleich-dot-cdist/files/etcd/ca.pem --etcd-cert-cert /home/nico/vcs/ungleich-dot-cdist/files/etcd/nico.pem --etcd-cert-key /home/nico/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem --last-used-mac
2020-01-14 19:02:15 +01:00
Nico Schottelius 1b36c2f96f Write VM to etcd 2020-01-14 14:23:26 +01:00
Nico Schottelius c0e6d6a0d8 Begin further integration of code into hack 2020-01-14 11:25:06 +01:00
Nico Schottelius 083ba43918 Integrate hack + vm create into python code 2020-01-14 11:22:04 +01:00
Nico Schottelius 22531a7459 Disable cli / otp reading for the moment
Imho this should clearly not leak into scripts/uncloud and
additionally it is broken at the moment
2020-01-14 11:09:45 +01:00
Nico Schottelius b96e56b453 Begin to integrate hack into the main script 2020-01-14 11:05:42 +01:00
Nico Schottelius 9f02b31b1b Add hacky etcd client 2020-01-13 12:54:02 +01:00
Nico Schottelius 10c8dc85ba Begin hacky database handling 2020-01-13 12:14:30 +01:00
Nico Schottelius 091131d350 dummy 2020-01-13 11:52:40 +01:00
Ahmed Bilal ab65349047 Merge branch 'conf-dir' into 'master'
Adding conf-dir and etcd-* arguments to command-line-interface

See merge request uncloud/uncloud!2
2020-01-13 05:57:42 +01:00
Ahmed Bilal c3b42aabc6 Added --conf-dir, --etcd-{host,port,ca_cert,cert_cert,cert_key} parameters to cli and settings is now accessbile through uncloud.shared.shared.settings 2020-01-13 05:57:41 +01:00
Nico Schottelius e6d22a73c5 ++ cleanup 2020-01-12 14:44:53 +01:00
Nico Schottelius 02526baaf9 add ifdown support 2020-01-12 14:43:06 +01:00
Nico Schottelius 3188787c2a ++mac change 2020-01-12 14:38:01 +01:00
Nico Schottelius 94dad7c9b6 Add script to generate mac addresses 2020-01-12 14:35:59 +01:00
Nico Schottelius 53c6a14d60 mac: begin to downstrip 2020-01-12 14:03:04 +01:00
Nico Schottelius 64ab011299 import mac.py from cinv 2020-01-12 13:41:54 +01:00
Nico Schottelius b017df4879 ignore iso, update nft rules 2020-01-12 13:20:38 +01:00
Nico Schottelius aaf29adcbb + mac prefix 2020-01-12 00:41:31 +01:00
Nico Schottelius 6d51e2a8c4 [metadata] change default port to 1234 2020-01-12 00:32:17 +01:00
Nico Schottelius c6b7152464 update nftrules example 2020-01-11 21:21:30 +01:00
Nico Schottelius 8544df8bad don't use tcg 2020-01-11 16:36:41 +01:00
Nico Schottelius 708e3ebb97 cleanup ifup.sh 2020-01-11 16:20:29 +01:00
Nico Schottelius 3b68a589d4 cleanup vm.sh 2020-01-11 16:17:35 +01:00
Nico Schottelius 029ef36d62 net +debug 2020-01-11 15:54:19 +01:00
Nico Schottelius 3cf4807f7c Merge branch 'master' of code.ungleich.ch:uncloud/uncloud
flush ruleset
2020-01-11 02:43:39 +01:00
Nico Schottelius c1cabb7220 add working nft 2020-01-11 02:42:04 +01:00
Nico Schottelius 5d95f11b3d Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-11 00:24:25 +01:00
Nico Schottelius 23d805f04f ++stuff 2020-01-11 00:24:17 +01:00
Nico Schottelius 3825c7c210 Add vxlan into the bridge 2020-01-11 00:23:55 +01:00
Nico Schottelius 7c9e3d747a Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-11 00:06:29 +01:00
Nico Schottelius b9c9a5e0ec add working network 2020-01-10 23:55:21 +01:00
Nico Schottelius ebcb1680d7 add hack scripts 2020-01-10 23:27:21 +01:00
ahmadbilalkhalid cf4930ee84 cli enabled again 2020-01-10 16:42:07 +05:00
ahmadbilalkhalid 00d876aea1 Do not break if client section/or OTP creds missing from conf file 2020-01-10 16:39:40 +05:00
Nico Schottelius e91fd9e24a disable cli until bug #25 is fixed 2020-01-10 12:00:02 +01:00
Nico Schottelius 469d03467d Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-01-10 11:56:56 +01:00
Nico Schottelius ec66a756a0 ++confdir 2020-01-10 11:56:47 +01:00
ahmadbilalkhalid b4f47adb4f print message removed 2020-01-10 15:47:38 +05:00
ahmadbilalkhalid 31ec024be6 passing arguments dict to componenets instead of **kwargs 2020-01-10 15:45:48 +05:00
Nico Schottelius 82a69701ce catch etcd in scripts/ 2020-01-10 11:43:53 +01:00
Nico Schottelius d9dd6b48dc No try: needed for pop/importlib/getattr 2020-01-10 11:35:04 +01:00
Nico Schottelius b7596e071a begin phasing in arguments instead of **arguments 2020-01-10 11:30:23 +01:00
Nico Schottelius 71fd0ca7d9 Remove double try/except blocks (with wraps) 2020-01-10 11:00:00 +01:00
Nico Schottelius 92f985c857 Handle etcd connection error 2020-01-10 10:10:37 +01:00
Nico Schottelius feb334cf04 Exit code == 1 in case we died with an exception 2020-01-10 10:07:01 +01:00
Nico Schottelius 388127bd11 [hack] add scripts to start VM 2020-01-05 18:32:14 +01:00
272 changed files with 14630 additions and 340 deletions

13
.gitignore vendored
View File

@ -1,6 +1,12 @@
.idea
.vscode
.idea/
.vscode/
__pycache__/
pay.conf
log.txt
test.py
STRIPE
venv/
uncloud/docs/build
logs.txt
@ -16,3 +22,6 @@ uncloud/version.py
build/
venv/
dist/
*.iso
*.sqlite3

18
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,18 @@
stages:
- lint
- test
run-tests:
stage: test
image: code.ungleich.ch:5050/uncloud/uncloud/uncloud-ci:latest
services:
- postgres:latest
variables:
DATABASE_HOST: postgres
DATABASE_USER: postgres
POSTGRES_HOST_AUTH_METHOD: trust
coverage: /^TOTAL.+?(\d+\%)$/
script:
- pip install -r requirements.txt
- coverage run --source='.' ./manage.py test
- coverage report

View File

@ -1,3 +1,62 @@
# ucloud
# Uncloud
Checkout https://ungleich.ch/ucloud/ for the documentation of ucloud.
Cloud management platform, the ungleich way.
[![pipeline status](https://code.ungleich.ch/uncloud/uncloud/badges/master/pipeline.svg)](https://code.ungleich.ch/uncloud/uncloud/commits/master)
[![coverage report](https://code.ungleich.ch/uncloud/uncloud/badges/master/coverage.svg)](https://code.ungleich.ch/uncloud/uncloud/commits/master)
## Useful commands
* `./manage.py import-vat-rates path/to/csv`
* `./manage.py make-admin username`
## Development setup
Install system dependencies:
* On Fedora, you will need the following packages: `python3-virtualenv python3-devel openldap-devel gcc chromium`
NOTE: you will need to configure a LDAP server and credentials for authentication. See `uncloud/settings.py`.
```
# Initialize virtualenv.
» virtualenv .venv
Using base prefix '/usr'
New python executable in /home/fnux/Workspace/ungleich/uncloud/uncloud/.venv/bin/python3
Also creating executable in /home/fnux/Workspace/ungleich/uncloud/uncloud/.venv/bin/python
Installing setuptools, pip, wheel...
done.
# Enter virtualenv.
» source .venv/bin/activate
# Install dependencies.
» pip install -r requirements.txt
[...]
# Run migrations.
» ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, opennebula, sessions, uncloud_auth, uncloud_net, uncloud_pay, uncloud_service, uncloud_vm
Running migrations:
[...]
# Run webserver.
» ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 07, 2020 - 10:17:08
Django version 3.0.6, using settings 'uncloud.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```
### Note on PGSQL
If you want to use Postgres:
* Install on configure PGSQL on your base system.
* OR use a container! `podman run --rm -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust -it postgres:latest`

6
archive/issues.org Normal file
View File

@ -0,0 +1,6 @@
* Intro
This file lists issues that should be handled, are small and likely
not yet high prio.
* Issues
** TODO Register prefered address in User model
** TODO Allow to specify different recurring periods

View File

@ -0,0 +1,55 @@
"""
investigate into a simple python function that maps an ldap user to a vat percentage. Basically you need to
lookup the customer address, check if she is a business/registered tax number and if not apply the local
vat
"""
import iso3166
import datetime
from csv import DictReader
def get_vat(street_address, city, postal_code, country, vat_number=None):
vat = {
'Austria': [
{'period': '1984-01-01/', 'rate': 0.2},
{'period': '1976-01-01/1984-01-01', 'rate': 0.18},
{'period': '1973-01-01/1976-01-01', 'rate': 0.16},
]
}
return iso3166.countries.get(country)
# return iso3166.countries_by_name[country]
def main():
# vat = get_vat(
# street_address='82 Nasheman-e-Iqbal near Wapda Town',
# city='Lahore',
# postal_code=53700,
# country='Pakistan',
# )
# print(vat)
vat_rates = {}
with open('vat_rates.csv', newline='') as csvfile:
reader = DictReader(csvfile)
for row in reader:
territory_codes = row['territory_codes'].split('\n')
for code in territory_codes:
if code not in vat_rates:
vat_rates[code] = {}
start_date = row['start_date']
stop_data = row['stop_date']
time_period = f'{start_date}|{stop_data}'
r = row.copy()
del r['start_date']
del r['stop_date']
del r['territory_codes']
vat_rates[code][time_period] = r
print(vat_rates)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,46 @@
import importlib
import sys
import os
from os.path import join as join_path
from xmlrpc.client import ServerProxy as RPCClient
root = os.path.dirname(os.getcwd())
sys.path.append(join_path(root, 'uncloud'))
secrets = importlib.import_module('uncloud.secrets')
class OpenNebula:
def __init__(self, url, session_string):
self.session_string = session_string
self.client = RPCClient(secrets.OPENNEBULA_URL)
def create_user(self, username, password, authentication_driver='', group_id=None):
# https://docs.opennebula.org/5.10/integration/system_interfaces/api.html#one-user-allocate
if group_id is None:
group_id = []
return self.client.one.user.allocate(
self.session_string,
username,
password,
authentication_driver,
group_id
)
def chmod(self, vm_id, user_id=-1, group_id=-1):
# https://docs.opennebula.org/5.10/integration/system_interfaces/api.html#one-vm-chown
return self.client.one.vm.chown(self.session_string, vm_id, user_id, group_id)
one = OpenNebula(secrets.OPENNEBULA_URL, secrets.OPENNEBULA_USER_PASS)
# Create User in OpenNebula
# success, response, *_ = one.create_user(username='meow12345', password='hello_world')
# print(success, response)
# Change owner of a VM
# success, response, *_ = one.chmod(vm_id=25589, user_id=706)
# print(success, response)

View File

@ -0,0 +1,18 @@
#!/bin/sh
dbhost=$1; shift
ssh -L5432:localhost:5432 "$dbhost" &
python manage.py "$@"
# command only needs to be active while manage command is running
# -T no pseudo terminal
# alternatively: commands output shell code
# ssh uncloud@dbhost "python manage.py --hostname xxx ..."

View File

@ -0,0 +1,51 @@
# uncloud-pay
The generic product/payment system.
## Installation
```shell script
pip3 install -r requirements.txt
```
## Getting Started
```shell script
python ucloud_pay.py
```
## Usage
#### 1. Adding of products
```shell script
http --json http://[::]:5000/product/add username=your_username_here password=your_password_here specs:=@ipv6-only-vm.json
```
#### 2. Listing of products
```shell script
http --json http://[::]:5000/product/list
```
#### 3. Registering user's payment method (credit card for now using Stripe)
```shell script
http --json http://[::]:5000/user/register_payment card_number=4111111111111111 cvc=123 expiry_year=2020 expiry_month=8 card_holder_name="The test user" username=your_username_here password=your_password_here line1="your_billing_address" city="your_city" country="your_country"
```
#### 4. Ordering products
First of all, user have to buy the membership first.
```shell script
http --json http://[::]:5000/product/order username=your_username_here password=your_password_here product_id=membership pay=True
```
```shell script
http --json http://[::]:5000/product/order username=your_username_here password=your_password_here product_id=ipv6-only-vm cpu=1 ram=1 os-disk-space=10 os=alpine pay=True
```
#### 5. Listing users orders
```shell script
http --json POST http://[::]:5000/order/list username=your_username_here password=your_password_here
```

View File

@ -0,0 +1,21 @@
import os
from ungleich_common.ldap.ldap_manager import LdapManager
from ungleich_common.std.configparser import StrictConfigParser
from ungleich_common.etcd.etcd_wrapper import EtcdWrapper
config_file = os.environ.get('meow-pay-config-file', default='pay.conf')
config = StrictConfigParser(allow_no_value=True)
config.read(config_file)
etcd_client = EtcdWrapper(
host=config.get('etcd', 'host'), port=config.get('etcd', 'port'),
ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'),
cert_cert=config.get('etcd', 'cert_cert')
)
ldap_manager = LdapManager(
server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'),
admin_password=config.get('ldap', 'admin_password')
)

View File

@ -0,0 +1,213 @@
from flask import Flask, request
from flask_restful import Resource, Api
import etcd3
import json
import logging
from functools import wraps
from ldaptest import is_valid_ldap_user
def authenticate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not getattr(func, 'authenticated', True):
return func(*args, **kwargs)
# pass in username/password !
acct = basic_authentication() # custom account lookup function
if acct:
return func(*args, **kwargs)
flask_restful.abort(401)
return wrapper
def readable_errors(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except etcd3.exceptions.ConnectionFailedError as e:
raise UncloudException('Cannot connect to etcd: is etcd running and reachable? {}'.format(e))
except etcd3.exceptions.ConnectionTimeoutError as e:
raise UncloudException('etcd connection timeout. {}'.format(e))
return wrapper
class DB(object):
def __init__(self, config, prefix="/"):
self.config = config
# Root for everything
self.base_prefix= '/nicohack'
# Can be set from outside
self.prefix = prefix
self.connect()
@readable_errors
def connect(self):
self._db_clients = []
for endpoint in self.config.etcd_hosts:
client = etcd3.client(host=endpoint, **self.config.etcd_args)
self._db_clients.append(client)
def realkey(self, key):
return "{}{}/{}".format(self.base_prefix,
self.prefix,
key)
@readable_errors
def get(self, key, as_json=False, **kwargs):
value, _ = self._db_clients[0].get(self.realkey(key), **kwargs)
if as_json:
value = json.loads(value)
return value
@readable_errors
def set(self, key, value, as_json=False, **kwargs):
if as_json:
value = json.dumps(value)
# FIXME: iterate over clients in case of failure ?
return self._db_clients[0].put(self.realkey(key), value, **kwargs)
class Membership(Resource):
def __init__(self, config):
self.config = config
def get(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return {'message': 'Order successful' }, 200
def post(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return {'message': 'Order 2x successful' }, 200
class Order(Resource):
def __init__(self, config):
self.config = config
@staticmethod
def post():
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
class Product(Resource):
def __init__(self, config):
self.config = config
self.products = []
self.products.append(
{ "name": "membership-free",
"description": """
This membership gives you access to the API and includes a VPN
with 1 IPv6 address.
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "a3883466-0012-4d01-80ff-cbf7469957af",
"recurring": True,
"recurring_time_frame": "per_year",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 0
}
]
}
)
self.products.append(
{ "name": "membership-standard",
"description": """
This membership gives you access to the API and includes an IPv6-VPN with
one IPv6 address ("Road warrior")
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "1d85296b-0863-4dd6-a543-a6d5a4fbe4a6",
"recurring": True,
"recurring_time_frame": "per_month",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 5
}
]
}
)
self.products.append(
{ "name": "membership-premium",
"description": """
This membership gives you access to the API and includes an
IPv6-VPN with a /48 IPv6 network.
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "bfd63fd2-d227-436f-a8b8-600de74dd6ce",
"recurring": True,
"recurring_time_frame": "per_month",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 5
}
]
}
)
self.products.append(
{ "name": "ipv6-vpn-with-/48",
"description": """
An IPv6 VPN with a /48 network included.
""",
"uuid": "fe5753f8-6fe1-4dc4-9b73-7b803de4c597",
"recurring": True,
"recurring_time_frame": "per_year",
"features": [
{ "name": "vpn",
"price_one_time": 0,
"price_recurring": 120
}
]
}
)
@staticmethod
def post():
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
def get(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return self.products
if __name__ == '__main__':
app = Flask(__name__)
config = {}
config['etcd_url']="https://etcd1.ungleich.ch"
config['ldap_url']="ldaps://ldap1.ungleich.ch"
api = Api(app)
api.add_resource(Order, '/orders', resource_class_args=( config, ))
api.add_resource(Product, '/products', resource_class_args=( config, ))
api.add_resource(Membership, '/membership', resource_class_args=( config, ))
app.run(host='::', port=5000, debug=True)

View File

@ -0,0 +1,87 @@
import logging
import parsedatetime
from datetime import datetime
from stripe_utils import StripeUtils
def get_plan_id_from_product(product):
plan_id = 'ucloud-v1-'
plan_id += product['name'].strip().replace(' ', '-')
return plan_id
def get_pricing(price_in_chf_cents, product_type, recurring_period):
if product_type == 'recurring':
return 'CHF {}/{}'.format(price_in_chf_cents/100, recurring_period)
elif product_type == 'one-time':
return 'CHF {} (One time charge)'.format(price_in_chf_cents/100)
def get_user_friendly_product(product_dict):
uf_product = {
'name': product_dict['name'],
'description': product_dict['description'],
'product_id': product_dict['usable-id'],
'pricing': get_pricing(
product_dict['price'], product_dict['type'], product_dict['recurring_period']
)
}
if product_dict['type'] == 'recurring':
uf_product['minimum_subscription_period'] = product_dict['minimum_subscription_period']
return uf_product
def get_token(card_number, cvc, exp_month, exp_year):
stripe_utils = StripeUtils()
token_response = stripe_utils.get_token_from_card(
card_number, cvc, exp_month, exp_year
)
if token_response['response_object']:
return token_response['response_object'].id
else:
return None
def resolve_product(usable_id, etcd_client):
products = etcd_client.get_prefix('/v1/products/', value_in_json=True)
for p in products:
if p.value['usable-id'] == usable_id:
return p.value
return None
def calculate_charges(specification, data):
logging.debug('Calculating charges for specs:{} and data:{}'.format(specification, data))
one_time_charge = 0
recurring_charge = 0
for feature_name, feature_detail in specification['features'].items():
if feature_detail['constant']:
data[feature_name] = 1
if feature_detail['unit']['type'] != 'str':
one_time_charge += feature_detail['one_time_fee']
recurring_charge += (
feature_detail['price_per_unit_per_period'] * data[feature_name] /
feature_detail['unit']['value']
)
return one_time_charge, recurring_charge
def is_order_valid(order_timestamp, renewal_period):
"""
Sample Code Usage
>> current_datetime, status = cal.parse('Now')
>> current_datetime = datetime(*current_datetime[:6])
>> print('Is order valid: ', is_order_valid(current_datetime, '1 month'))
>> True
"""
cal = parsedatetime.Calendar()
renewal_datetime, status = cal.parse(renewal_period)
renewal_datetime = datetime(*renewal_datetime[:6])
return order_timestamp <= renewal_datetime

View File

@ -0,0 +1,28 @@
{
"usable-id": "ipv6-only-django-hosting",
"active": true,
"name": "IPv6 Only Django Hosting",
"description": "Host your Django application on our shiny IPv6 Only VM",
"recurring_period": "month",
"quantity": "inf",
"features": {
"cpu": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 3,
"one_time_fee": 0,
"constant": false
},
"ram": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 4,
"one_time_fee": 0,
"constant": false
},
"os-disk-space": {
"unit": {"value": 10, "type":"int"},
"one_time_fee": 0,
"price_per_unit_per_period": 3.5,
"constant": false
}
}
}

View File

@ -0,0 +1,34 @@
{
"usable-id": "ipv6-only-vm",
"active": true,
"name": "IPv6 Only VM",
"description": "IPv6 Only VM are accessible to only those having IPv6 for themselves",
"recurring_period": "month",
"quantity": "inf",
"features": {
"cpu": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 3,
"one_time_fee": 0,
"constant": false
},
"ram": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 4,
"one_time_fee": 0,
"constant": false
},
"os-disk-space": {
"unit": {"value": 10, "type":"int"},
"one_time_fee": 0,
"price_per_unit_per_period": 4,
"constant": false
},
"os": {
"unit": {"value": 1, "type":"str"},
"one_time_fee": 0,
"price_per_unit_per_period": 0,
"constant": false
}
}
}

View File

@ -0,0 +1,16 @@
{
"usable-id": "ipv6-only-vpn",
"active": true,
"name": "IPv6 Only VPN",
"description": "IPv6 VPN enable you to access IPv6 only websites and more",
"recurring_period": "month",
"quantity": "inf",
"features": {
"vpn": {
"unit": {"value": 1, "type": "int"},
"price_per_unit_per_period": 10,
"one_time_fee": 0,
"constant": true
}
}
}

View File

@ -0,0 +1,16 @@
{
"usable-id": "ipv6-box",
"active": true,
"name": "IPv6 Box",
"description": "A ready-to-go IPv6 Box: it creates a VPN to ungleich and distributes IPv6 addresses to all your computers.",
"recurring_period": "eternity",
"quantity": 4,
"features": {
"ipv6-box": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 0,
"one_time_fee": 250,
"constant": true
}
}
}

View File

@ -0,0 +1,17 @@
{
"usable-id": "membership",
"active": true,
"name": "Membership",
"description": "Membership to use uncloud-pay",
"recurring_period": "month",
"quantity": "inf",
"features": {
"membership": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 5,
"one_time_fee": 0,
"constant": true
}
},
"max_per_user": "1"
}

View File

@ -0,0 +1,7 @@
stripe
flask
Flask-RESTful
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-etcd&subdirectory=etcd
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-ldap&subdirectory=ldap
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-std&subdirectory=std
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-schemas&subdirectory=schemas

View File

@ -0,0 +1,17 @@
[etcd]
host = 127.0.0.1
port = 2379
ca_cert
cert_cert
cert_key
[stripe]
private_key=stripe_private_key
[app]
port = 5000
[ldap]
server = ldap_server_url
admin_dn = ldap_admin_dn
admin_password = ldap_admin_password

View File

@ -0,0 +1,136 @@
import logging
import config
import json
import math
from config import ldap_manager, etcd_client
from helper import resolve_product
from ungleich_common.schemas.schemas import BaseSchema, Field, ValidationException
class AddProductSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
self.specs = Field('specs', dict, **self.get(data, 'specs'))
self.update = Field('update', bool, **self.get(data, 'update', return_default=True, default=False))
def validation(self):
user = self.objects['user']
user = json.loads(user.entry_to_json())
uid, ou, *dc = user['dn'].replace('ou=', '').replace('dc=', '').replace('uid=', '').split(',')
if ou != config.config.get('ldap', 'internal_user_ou', fallback='users'):
raise ValidationException('You do not have access to create product.')
product = resolve_product(self.specs.value['usable-id'], etcd_client)
if product:
self.objects['product'] = product
class AddressSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.line1 = Field('line1', str, **self.get(data, 'line1'))
self.line2 = Field('line2', str, **self.get(data, 'line2', return_default=True))
self.city = Field('city', str, **self.get(data, 'city'))
self.country = Field('country', str, **self.get(data, 'country'))
self.state = Field('state', str, **self.get(data, 'state', return_default=True))
self.postal_code = Field('postal_code', str, **self.get(data, 'postal_code', return_default=True))
class UserRegisterPaymentSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
self.add_schema(AddressSchema, data, under_field_name='address')
self.card_number = Field('card_number', str, **self.get(data, 'card_number'))
self.cvc = Field('cvc', str, **self.get(data, 'cvc'))
self.expiry_year = Field('expiry_year', int, **self.get(data, 'expiry_year'))
self.expiry_month = Field('expiry_month', int, **self.get(data, 'expiry_month'))
self.card_holder_name = Field('card_holder_name', str, **self.get(data, 'card_holder_name'))
class UserCredentialSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.username = Field('username', str, **self.get(data, 'username'))
self.password = Field('password', str, **self.get(data, 'password'))
def validation(self):
try:
entry = ldap_manager.is_password_valid(self.username.value, self.password.value, query_key='uid')
except ValueError:
raise ValidationException('No user with \'{}\' username found. You can create account at '
'https://account.ungleich.ch'.format(self.username.value))
except Exception:
raise ValidationException('Invalid username/password.')
else:
self.objects['user'] = entry
class ProductOrderSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.product_id = Field(
'product_id', str, **self.get(data, 'product_id'), validators=[self.product_id_validation]
)
self.pay_consent = Field('pay', bool, **self.get(data, 'pay', return_default=True, default=False))
self.add_schema(UserCredentialSchema, data)
def product_id_validation(self):
product = resolve_product(self.product_id.value, etcd_client)
if product:
product['quantity'] = float(product['quantity'])
self.product_id.value = product['uuid']
self.objects['product'] = product
logging.debug('Got product {}'.format(product))
if not product['active']:
raise ValidationException('Product is not active at the moment.')
if product['quantity'] <= 0:
raise ValidationException('Out of stock.')
else:
raise ValidationException('No such product exists.')
def validation(self):
username = self.objects['user'].uid
customer_previous_orders = etcd_client.get_prefix('/v1/user/{}'.format(username), value_in_json=True)
customer_previous_orders = [o.value for o in customer_previous_orders]
membership = next(filter(lambda o: o['product'] == 'membership', customer_previous_orders), None)
if membership is None and self.objects['product']['usable-id'] != 'membership':
raise ValidationException('Please buy membership first to use this facility')
max_quantity_user_can_order = float(self.objects['product'].get('max_per_user', math.inf))
previous_order_of_same_product = [
o for o in customer_previous_orders if o['product'] == self.objects['product']['usable-id']
]
if len(previous_order_of_same_product) >= max_quantity_user_can_order:
raise ValidationException(
'You cannot buy {} more than {} times'.format(
self.objects['product']['name'], int(max_quantity_user_can_order)
)
)
class OrderListSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
def make_return_message(err, status_code=200):
logging.debug('message: {}'.format(str(err)))
return {'message': str(err)}, status_code
def create_schema(specification, data):
fields = {}
for feature_name, feature_detail in specification['features'].items():
if not feature_detail['constant']:
fields[feature_name] = Field(
feature_name, eval(feature_detail['unit']['type']), **BaseSchema.get(data, feature_name)
)
return type('{}Schema'.format(specification['name']), (BaseSchema,), fields)

View File

@ -0,0 +1,7 @@
import stripe_utils
import os
if __name__ == '__main__':
s = stripe_utils.StripeUtils(os.environ['STRIPE_PRIVATE_KEY'])
print(s.get_stripe_customer_from_email('coder.purple+2002@gmail.com'))

View File

@ -0,0 +1,491 @@
import re
import stripe
import stripe.error
import logging
from config import etcd_client as client, config as config
stripe.api_key = config.get('stripe', 'private_key')
def handle_stripe_error(f):
def handle_problems(*args, **kwargs):
response = {
'paid': False,
'response_object': None,
'error': None
}
common_message = "Currently it's not possible to make payments."
try:
response_object = f(*args, **kwargs)
response = {
'response_object': response_object,
'error': None
}
return response
except stripe.error.CardError as e:
# Since it's a decline, stripe.error.CardError will be caught
body = e.json_body
err = body['error']
response.update({'error': err['message']})
logging.error(str(e))
return response
except stripe.error.RateLimitError:
response.update(
{'error': "Too many requests made to the API too quickly"})
return response
except stripe.error.InvalidRequestError as e:
logging.error(str(e))
response.update({'error': "Invalid parameters"})
return response
except stripe.error.AuthenticationError as e:
# Authentication with Stripe's API failed
# (maybe you changed API keys recently)
logging.error(str(e))
response.update({'error': common_message})
return response
except stripe.error.APIConnectionError as e:
logging.error(str(e))
response.update({'error': common_message})
return response
except stripe.error.StripeError as e:
# maybe send email
logging.error(str(e))
response.update({'error': common_message})
return response
except Exception as e:
# maybe send email
logging.error(str(e))
response.update({'error': common_message})
return response
return handle_problems
class StripeUtils(object):
CURRENCY = 'chf'
INTERVAL = 'month'
SUCCEEDED_STATUS = 'succeeded'
STRIPE_PLAN_ALREADY_EXISTS = 'Plan already exists'
STRIPE_NO_SUCH_PLAN = 'No such plan'
PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.'
PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.'
def __init__(self, private_key):
self.stripe = stripe
stripe.api_key = private_key
@handle_stripe_error
def card_exists(self, customer, cc_number, exp_month, exp_year, cvc):
token_obj = stripe.Token.create(
card={
'number': cc_number,
'exp_month': exp_month,
'exp_year': exp_year,
'cvc': cvc,
},
)
cards = stripe.Customer.list_sources(
customer,
limit=20,
object='card'
)
for card in cards.data:
if (card.fingerprint == token_obj.card.fingerprint and
int(card.exp_month) == int(exp_month) and int(card.exp_year) == int(exp_year)):
return True
return False
@staticmethod
def get_stripe_customer_from_email(email):
customer = stripe.Customer.list(limit=1, email=email)
return customer.data[0] if len(customer.data) == 1 else None
@staticmethod
def update_customer_token(customer, token):
customer.source = token
customer.save()
@handle_stripe_error
def get_token_from_card(self, cc_number, cvc, expiry_month, expiry_year):
token_obj = stripe.Token.create(
card={
'number': cc_number,
'exp_month': expiry_month,
'exp_year': expiry_year,
'cvc': cvc,
},
)
return token_obj
@handle_stripe_error
def associate_customer_card(self, stripe_customer_id, token,
set_as_default=False):
customer = stripe.Customer.retrieve(stripe_customer_id)
card = customer.sources.create(source=token)
if set_as_default:
customer.default_source = card.id
customer.save()
return True
@handle_stripe_error
def dissociate_customer_card(self, stripe_customer_id, card_id):
customer = stripe.Customer.retrieve(stripe_customer_id)
card = customer.sources.retrieve(card_id)
card.delete()
@handle_stripe_error
def update_customer_card(self, customer_id, token):
customer = stripe.Customer.retrieve(customer_id)
current_card_token = customer.default_source
customer.sources.retrieve(current_card_token).delete()
customer.source = token
customer.save()
credit_card_raw_data = customer.sources.data.pop()
new_card_data = {
'last4': credit_card_raw_data.last4,
'brand': credit_card_raw_data.brand
}
return new_card_data
@handle_stripe_error
def get_card_details(self, customer_id):
customer = stripe.Customer.retrieve(customer_id)
credit_card_raw_data = customer.sources.data.pop()
card_details = {
'last4': credit_card_raw_data.last4,
'brand': credit_card_raw_data.brand,
'exp_month': credit_card_raw_data.exp_month,
'exp_year': credit_card_raw_data.exp_year,
'fingerprint': credit_card_raw_data.fingerprint,
'card_id': credit_card_raw_data.id
}
return card_details
@handle_stripe_error
def get_all_invoices(self, customer_id, created_gt):
return_list = []
has_more_invoices = True
starting_after = False
while has_more_invoices:
if starting_after:
invoices = stripe.Invoice.list(
limit=10, customer=customer_id, created={'gt': created_gt},
starting_after=starting_after
)
else:
invoices = stripe.Invoice.list(
limit=10, customer=customer_id, created={'gt': created_gt}
)
has_more_invoices = invoices.has_more
for invoice in invoices.data:
sub_ids = []
for line in invoice.lines.data:
if line.type == 'subscription':
sub_ids.append(line.id)
elif line.type == 'invoiceitem':
sub_ids.append(line.subscription)
else:
sub_ids.append('')
invoice_details = {
'created': invoice.created,
'receipt_number': invoice.receipt_number,
'invoice_number': invoice.number,
'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0,
'period_start': invoice.period_start,
'period_end': invoice.period_end,
'billing_reason': invoice.billing_reason,
'discount': invoice.discount.coupon.amount_off if invoice.discount else 0,
'total': invoice.total,
# to see how many line items we have in this invoice and
# then later check if we have more than 1
'lines_data_count': len(invoice.lines.data) if invoice.lines.data is not None else 0,
'invoice_id': invoice.id,
'lines_meta_data_csv': ','.join(
[line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data]
),
'subscription_ids_csv': ','.join(sub_ids),
'line_items': invoice.lines.data
}
starting_after = invoice.id
return_list.append(invoice_details)
return return_list
@handle_stripe_error
def get_cards_details_from_token(self, token):
stripe_token = stripe.Token.retrieve(token)
card_details = {
'last4': stripe_token.card.last4,
'brand': stripe_token.card.brand,
'exp_month': stripe_token.card.exp_month,
'exp_year': stripe_token.card.exp_year,
'fingerprint': stripe_token.card.fingerprint,
'card_id': stripe_token.card.id
}
return card_details
def check_customer(self, stripe_cus_api_id, user, token):
try:
customer = stripe.Customer.retrieve(stripe_cus_api_id)
except stripe.error.InvalidRequestError:
customer = self.create_customer(token, user.email, user.name)
user.stripecustomer.stripe_id = customer.get(
'response_object').get('id')
user.stripecustomer.save()
if type(customer) is dict:
customer = customer['response_object']
return customer
@handle_stripe_error
def get_customer(self, stripe_api_cus_id):
customer = stripe.Customer.retrieve(stripe_api_cus_id)
# data = customer.get('response_object')
return customer
@handle_stripe_error
def create_customer(self, token, email, name=None, address=None):
if name is None or name.strip() == "":
name = email
customer = self.stripe.Customer.create(
source=token,
description=name,
email=email,
address=address
)
return customer
@handle_stripe_error
def make_charge(self, amount=None, customer=None):
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
charge = self.stripe.Charge.create(
amount=amount, # in cents
currency=self.CURRENCY,
customer=customer
)
return charge
@staticmethod
def _get_all_stripe_plans():
all_stripe_plans = client.get("/v1/stripe_plans")
all_stripe_plans_set = set()
if all_stripe_plans:
all_stripe_plans_obj = all_stripe_plans.value
if all_stripe_plans_obj and len(all_stripe_plans_obj['plans']) > 0:
all_stripe_plans_set = set(all_stripe_plans_obj["plans"])
return all_stripe_plans_set
@staticmethod
def _save_all_stripe_plans(stripe_plans):
client.put("/v1/stripe_plans", {"plans": list(stripe_plans)})
@handle_stripe_error
def get_or_create_stripe_plan(self, product_name, amount, stripe_plan_id,
interval=INTERVAL):
"""
This function checks if a StripePlan with the given
stripe_plan_id already exists. If it exists then the function
returns this object otherwise it creates a new StripePlan and
returns the new object.
:param amount: The amount in CHF cents
:param product_name: The name of the Stripe plan (product) to be created.
:param stripe_plan_id: The id of the Stripe plan to be
created. Use get_stripe_plan_id_string function to
obtain the name of the plan to be created
:param interval: The interval for subscription {month, year}. Defaults
to month if not provided
:return: The StripePlan object if it exists else creates a
Plan object in Stripe and a local StripePlan and
returns it. Returns None in case of Stripe error
"""
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
all_stripe_plans = self._get_all_stripe_plans()
if stripe_plan_id in all_stripe_plans:
logging.debug("{} plan exists in db.".format(stripe_plan_id))
else:
logging.debug(("{} plan DOES NOT exist in db. "
"Creating").format(stripe_plan_id))
try:
plan_obj = self.stripe.Plan.retrieve(id=stripe_plan_id)
logging.debug("{} plan exists in Stripe".format(stripe_plan_id))
all_stripe_plans.add(stripe_plan_id)
except stripe.error.InvalidRequestError as e:
if "No such plan" in str(e):
logging.debug("Plan {} does not exist in Stripe, Creating")
plan_obj = self.stripe.Plan.create(
amount=amount,
product={'name': product_name},
interval=interval,
currency=self.CURRENCY,
id=stripe_plan_id)
logging.debug(self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id))
all_stripe_plans.add(stripe_plan_id)
self._save_all_stripe_plans(all_stripe_plans)
return stripe_plan_id
@handle_stripe_error
def delete_stripe_plan(self, stripe_plan_id):
"""
Deletes the Plan in Stripe and also deletes the local db copy
of the plan if it exists
:param stripe_plan_id: The stripe plan id that needs to be
deleted
:return: True if the plan was deleted successfully from
Stripe, False otherwise.
"""
return_value = False
try:
plan = self.stripe.Plan.retrieve(stripe_plan_id)
plan.delete()
return_value = True
all_stripe_plans = self._get_all_stripe_plans()
all_stripe_plans.remove(stripe_plan_id)
self._save_all_stripe_plans(all_stripe_plans)
except stripe.error.InvalidRequestError as e:
if self.STRIPE_NO_SUCH_PLAN in str(e):
logging.debug(
self.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(stripe_plan_id))
return return_value
@handle_stripe_error
def subscribe_customer_to_plan(self, customer, plans, trial_end=None):
"""
Subscribes the given customer to the list of given plans
:param customer: The stripe customer identifier
:param plans: A list of stripe plans.
:param trial_end: An integer representing when the Stripe subscription
is supposed to end
Ref: https://stripe.com/docs/api/python#create_subscription-items
e.g.
plans = [
{
"plan": "dcl-v1-cpu-2-ram-5gb-ssd-10gb",
},
]
:return: The subscription StripeObject
"""
subscription_result = self.stripe.Subscription.create(
customer=customer, items=plans, trial_end=trial_end
)
return subscription_result
@handle_stripe_error
def set_subscription_metadata(self, subscription_id, metadata):
subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = metadata
subscription.save()
@handle_stripe_error
def unsubscribe_customer(self, subscription_id):
"""
Cancels a given subscription
:param subscription_id: The Stripe subscription id string
:return:
"""
sub = stripe.Subscription.retrieve(subscription_id)
return sub.delete()
@handle_stripe_error
def make_payment(self, customer, amount, token):
charge = self.stripe.Charge.create(
amount=amount, # in cents
currency=self.CURRENCY,
customer=customer
)
return charge
@staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None,
price=None):
"""
Returns the Stripe plan id string of the form
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
:param cpu: The number of cores
:param ram: The size of the RAM in GB
:param ssd: The size of ssd storage in GB
:param hdd: The size of hdd storage in GB
:param version: The version of the Stripe plans
:param app: The application to which the stripe plan belongs
to. By default it is 'dcl'
:param price: The price for this plan
:return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb`
"""
dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu,
ram=ram,
ssd=ssd)
if hdd is not None:
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
dcl_plan_string=dcl_plan_string, hdd=hdd)
stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
app=app,
version=version,
plan=dcl_plan_string
)
if price is not None:
stripe_plan_id_string_with_price = '{}-{}chf'.format(
stripe_plan_id_string,
round(price, 2)
)
return stripe_plan_id_string_with_price
else:
return stripe_plan_id_string
@staticmethod
def get_vm_config_from_stripe_id(stripe_id):
"""
Given a string like "dcl-v1-cpu-2-ram-5gb-ssd-10gb" return different
configuration params as a dict
:param stripe_id|str
:return: dict
"""
pattern = re.compile(r'^dcl-v(\d+)-cpu-(\d+)-ram-(\d+\.?\d*)gb-ssd-(\d+)gb-?(\d*\.?\d*)(chf)?$')
match_res = pattern.match(stripe_id)
if match_res is not None:
price = None
try:
price = match_res.group(5)
except IndexError:
logging.debug("Did not find price in {}".format(stripe_id))
return {
'version': match_res.group(1),
'cores': match_res.group(2),
'ram': match_res.group(3),
'ssd': match_res.group(4),
'price': price
}
@staticmethod
def get_stripe_plan_name(cpu, memory, disk_size, price):
"""
Returns the Stripe plan name
:return:
"""
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
"{price} CHF".format(
cpu=cpu,
memory=memory,
disk_size=disk_size,
price=round(price, 2)
)
@handle_stripe_error
def set_subscription_meta_data(self, subscription_id, meta_data):
"""
Adds VM metadata to a subscription
:param subscription_id: Stripe identifier for the subscription
:param meta_data: A dict of meta data to be added
:return:
"""
subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = meta_data
subscription.save()

View File

@ -0,0 +1,338 @@
import logging
from datetime import datetime
from uuid import uuid4
from flask import Flask, request
from flask_restful import Resource, Api
from werkzeug.exceptions import HTTPException
from config import etcd_client as client, config as config
from stripe_utils import StripeUtils
from schemas import (
make_return_message, ValidationException, UserRegisterPaymentSchema,
AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema
)
from helper import get_plan_id_from_product, calculate_charges
class ListProducts(Resource):
@staticmethod
def get():
products = client.get_prefix('/v1/products/')
products = [
product
for product in [p.value for p in products]
if product['active']
]
prod_dict = {}
for p in products:
prod_dict[p['usable-id']] = {
'name': p['name'],
'description': p['description'],
}
logger.debug('Products = {}'.format(prod_dict))
return prod_dict, 200
class AddProduct(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
logger.debug('Got data: {}'.format(str(data)))
validator = AddProductSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
previous_product = cleaned_values.get('product', None)
if previous_product:
if not cleaned_values['update']:
return make_return_message('Product already exists. Pass --update to update the product.')
else:
product_uuid = previous_product.pop('uuid')
else:
product_uuid = uuid4().hex
product_value = cleaned_values['specs']
product_key = '/v1/products/{}'.format(product_uuid)
product_value['uuid'] = product_uuid
logger.debug('Adding product data: {}'.format(str(product_value)))
client.put(product_key, product_value)
if not previous_product:
return make_return_message('Product created.')
else:
return make_return_message('Product updated.')
################################################################################
# Nico-ok-marker
class UserRegisterPayment(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
logger.debug('Got data: {}'.format(str(data)))
validator = UserRegisterPaymentSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
last4 = data['card_number'].strip()[-4:]
stripe_utils = StripeUtils()
# Does customer already exist ?
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
# Does customer already exist ?
if stripe_customer is not None:
logger.debug('Customer {}-{} exists already'.format(
cleaned_values['username'], cleaned_values['user']['mail'])
)
# Check if the card already exists
ce_response = stripe_utils.card_exists(
stripe_customer.id, cc_number=data['card_number'],
exp_month=int(data['expiry_month']),
exp_year=int(data['expiry_year']),
cvc=data['cvc'])
if ce_response['response_object']:
message = 'The given card ending in {} exists already.'.format(last4)
return make_return_message(message, 400)
elif ce_response['response_object'] is False:
# Associate card with user
logger.debug('Adding card ending in {}'.format(last4))
token_response = stripe_utils.get_token_from_card(
data['card_number'], data['cvc'], data['expiry_month'],
data['expiry_year']
)
if token_response['response_object']:
logger.debug('Token {}'.format(token_response['response_object'].id))
resp = stripe_utils.associate_customer_card(
stripe_customer.id, token_response['response_object'].id
)
if resp['response_object']:
return make_return_message(
'Card ending in {} registered as your payment source'.format(last4)
)
else:
return make_return_message('Error with payment gateway. Contact support', 400)
else:
return make_return_message('Error: {}'.format(ce_response['error']), 400)
else:
# Stripe customer does not exist, create a new one
logger.debug(
'Customer {} does not exist, creating new'.format(cleaned_values['user']['mail'])
)
token_response = stripe_utils.get_token_from_card(
cleaned_values['card_number'], cleaned_values['cvc'],
cleaned_values['expiry_month'], cleaned_values['expiry_year']
)
if token_response['response_object']:
logger.debug('Token {}'.format(token_response['response_object'].id))
# Create stripe customer
stripe_customer_resp = stripe_utils.create_customer(
name=cleaned_values['card_holder_name'],
token=token_response['response_object'].id,
email=cleaned_values['user']['mail'],
address=cleaned_values['address']
)
stripe_customer = stripe_customer_resp['response_object']
if stripe_customer:
logger.debug('Created stripe customer {}'.format(stripe_customer.id))
return make_return_message(
'Card ending in {} registered as your payment source'.format(last4)
)
else:
return make_return_message('Error with card. Contact support', 400)
else:
return make_return_message('Error with payment gateway. Contact support', 400)
class ProductOrder(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
validator = ProductOrderSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
stripe_utils = StripeUtils()
product = cleaned_values['product']
# Check the user has a payment source added
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
if not stripe_customer or len(stripe_customer.sources) == 0:
return make_return_message('Please register your payment method first.', 400)
try:
product_schema = create_schema(product, data)
product_schema = product_schema()
product_schema.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
transformed_data = product_schema.get_cleaned_values()
logger.debug('Tranformed data: {}'.format(transformed_data))
one_time_charge, recurring_charge = calculate_charges(product, transformed_data)
recurring_charge = int(recurring_charge)
if not cleaned_values['pay']:
return make_return_message(
'You would be charged {} CHF one time and {} CHF every {}. '
'Add --pay to command to order.'.format(
one_time_charge, recurring_charge, product['recurring_period']
)
)
with client.client.lock('product-order') as _:
# Initiate a one-time/subscription based on product type
if recurring_charge > 0:
logger.debug('Product {} is recurring payment'.format(product['name']))
plan_id = get_plan_id_from_product(product)
res = stripe_utils.get_or_create_stripe_plan(
product_name=product['name'],
stripe_plan_id=plan_id, amount=recurring_charge,
interval=product['recurring_period'],
)
if res['response_object']:
logger.debug('Obtained plan {}'.format(plan_id))
subscription_res = stripe_utils.subscribe_customer_to_plan(
stripe_customer.id,
[{'plan': plan_id}]
)
subscription_obj = subscription_res['response_object']
if subscription_obj is None or subscription_obj.status != 'active':
return make_return_message(
'Error subscribing to plan. Detail: {}'.format(subscription_res['error']), 400
)
else:
order_obj = {
'order-id': uuid4().hex,
'ordered-at': datetime.now().isoformat(),
'product': product['usable-id'],
'one-time-price': one_time_charge,
'recurring-price': recurring_charge,
'recurring-period': product['recurring_period']
}
client.put(
'/v1/user/{}/orders/{}'.format(
cleaned_values['username'], order_obj['order-id']
), order_obj
)
product['quantity'] -= 1
client.put('/v1/products/{}'.format(product['uuid']), product)
return {
'message': 'Order Successful.',
**order_obj
}
else:
logger.error('Could not create plan {}'.format(plan_id))
return make_return_message('Something wrong happened. Contact administrator', 400)
elif recurring_charge == 0 and one_time_charge > 0:
logger.debug('Product {} is one-time payment'.format(product['name']))
charge_response = stripe_utils.make_charge(
amount=one_time_charge,
customer=stripe_customer.id
)
stripe_onetime_charge = charge_response.get('response_object')
# Check if the payment was approved
if not stripe_onetime_charge:
msg = charge_response.get('error')
return make_return_message('Error subscribing to plan. Details: {}'.format(msg), 400)
order_obj = {
'order-id': uuid4().hex,
'ordered-at': datetime.now().isoformat(),
'product': product['usable-id'],
'one-time-price': one_time_charge,
}
client.put(
'/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']),
order_obj
)
product['quantity'] -= 1
client.put('/v1/products/{}'.format(product['uuid']), product)
return {'message': 'Order successful', **order_obj}, 200
class OrderList(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
validator = OrderListSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
orders = client.get_prefix('/v1/user/{}/orders'.format(cleaned_values['username']))
orders_dict = {
order.value['order-id']: {
**order.value
}
for order in orders
}
logger.debug('Orders = {}'.format(orders_dict))
return {'orders': orders_dict}, 200
if __name__ == '__main__':
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
log_formater = logging.Formatter('[%(filename)s:%(lineno)d] %(message)s')
stream_logger = logging.StreamHandler()
stream_logger.setFormatter(log_formater)
# file_logger = logging.FileHandler('log.txt')
# file_logger.setLevel(logging.DEBUG)
# file_logger.setFormatter(log_formater)
logger.addHandler(stream_logger)
# logger.addHandler(file_logger)
app = Flask(__name__)
api = Api(app)
api.add_resource(ListProducts, '/product/list')
api.add_resource(AddProduct, '/product/add')
api.add_resource(ProductOrder, '/product/order')
api.add_resource(UserRegisterPayment, '/user/register_payment')
api.add_resource(OrderList, '/order/list')
app.run(host='::', port=config.get('app', 'port', fallback=5000), debug=True)
@app.errorhandler(Exception)
def handle_exception(e):
app.logger.error(e)
# pass through HTTP errors
if isinstance(e, HTTPException):
return e
# now you're handling non-HTTP exceptions only
return {'message': 'Server Error'}, 500

View File

@ -0,0 +1,11 @@
## TODO 2020-02-22
* ~~move the current rest api to /opennebula~~
* ~~make the /opennebula api only accessible by an admin account~~
* ~~create a new filtered api on /vm/list that~~
* ~~a) requires authentication~~
* ~~b) only shows the VMs of the current user~~
* ~~the new api should not contain all details, but: cpus (as read by the vcpu field), ram, ips, disks~~
* ~~also make a (random) uuid the primary key for VMs - everything in this uncloud hack will use uuids as the id~~
* ~~still expose the opennebula id as opennebula_id~~
* ~~note put all secrets/configs into uncloud.secrets - I added a sample file into the repo~~

View File

@ -0,0 +1,102 @@
* snapshot feature
** product: vm-snapshot
** flow
*** list all my VMs
**** get the uuid of the VM I want to take a snapshot of
*** request a snapshot
```
vmuuid=$(http nicocustomer
http -a nicocustomer:xxx http://uncloud.ch/vm/create_snapshot uuid=
password=...
```
** backend realisation
*** list snapshots
- have them in the DB
- create an entry on create
*** creating snapshots
- vm sync / fsync?
- rbd snapshot
- host/cluster mapping?
- need image(s)
* steps
** DONE authenticate via ldap
CLOSED: [2020-02-20 Thu 19:05]
** DONE Make classes / views require authentication
CLOSED: [2020-02-20 Thu 19:05]
** TODO register credit card
*** TODO find out what saving with us
*** Info
**** should not be fully saved in the DB
**** model needs to be a bit different
* Decide where to save sensitive data
** stripe access key, etc.
* python requirements (nicohack202002)
django djangorestframework django-auth-ldap stripe
* os package requirements (alpine)
openldap-dev
* VPN case
** put on /orders with uuid
** register cc
* CC
** TODO check whether we can register or not at stripe
* membership
** required for "smaller" / "shorter" products
* TODO Membership missing
* Flows to be implemented - see https://redmine.ungleich.ch/issues/7609
** Membership
*** 5 CHF
** Django Hosting
*** One time payment 35 CHF
*** Monthly payment depends on VM size
*** Parameters: same as IPv6 only VM
** IPv6 VPN
*** Parameters: none
*** Is for free if the customer has an active VM
** IPv6 only VM
*** Parameters: cores, ram, os_disk_size, OS
* Django rest framework
** viewset: .list and .create
** view: .get .post
* TODO register CC
* DONE list products
CLOSED: [2020-02-24 Mon 20:15]
* An ungleich account - can be registered for free on
https://account.ungleich.ch
* httpie installed (provides the http command)
## Get a membership
## Registering a payment method
To be able to pay for the membership, you will need to register a
credit card or apply for payment on bill (TO BE IMPLEMENTED).
### Register credit card
```
http POST https://api.ungleich.ch/membership \
username=nico password=yourpassword \
cc_number=.. \
cc_
```
### Request payment via bill
## Create the membership
```
http POST https://api.ungleich.ch/membership username=nico password=yourpassword
```
## List available products

View File

@ -0,0 +1,6 @@
* TODO register CC
* TODO list products
* ahmed
** schemas
*** field: is_valid? - used by schemas
*** definition of a "schema"

View File

@ -0,0 +1,4 @@
db.sqlite3
uncloud/secrets.py
debug.log
uncloud/local_settings.py

View File

@ -1,22 +1,22 @@
#!/bin/sh
# -*- coding: utf-8 -*-
#
# 2019 Nico Schottelius (nico-ucloud at schottelius.org)
# 2019-2020 Nico Schottelius (nico-uncloud at schottelius.org)
#
# This file is part of ucloud.
# This file is part of uncloud.
#
# ucloud is free software: you can redistribute it and/or modify
# uncloud is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ucloud is distributed in the hope that it will be useful,
# uncloud is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ucloud. If not, see <http://www.gnu.org/licenses/>.
# along with uncloud. If not, see <http://www.gnu.org/licenses/>.
#
#
@ -26,4 +26,4 @@ dir=${0%/*}
# Ensure version is present - the bundled/shipped version contains a static version,
# the git version contains a dynamic version
printf "VERSION = \"%s\"\n" "$(git describe)" > ${dir}/../uncloud/version.py
printf "VERSION = \"%s\"\n" "$(git describe --tags --abbrev=0)" > ${dir}/../uncloud/version.py

View File

@ -24,6 +24,6 @@
dir=${0%/*}
${dir}/gen-version;
pip uninstall -y uncloud
python setup.py install
pip uninstall -y uncloud >/dev/null
python setup.py install >/dev/null
${dir}/uncloud "$@"

View File

@ -7,7 +7,7 @@ SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source/
BUILDDIR = build/
DESTINATION=root@staticweb.ungleich.ch:/home/services/www/ungleichstatic/staticcms.ungleich.ch/www/ucloud/
DESTINATION=root@staticweb.ungleich.ch:/home/services/www/ungleichstatic/staticcms.ungleich.ch/www/uncloud/
.PHONY: all build clean

View File

@ -56,40 +56,13 @@ To start host we created earlier, execute the following command
ucloud host ungleich.ch
Create OS Image
---------------
File & image scanners
--------------------------
Create ucloud-init ready OS image (Optional)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This step is optional if you just want to test ucloud. However, sooner or later
you want to create OS images with ucloud-init to properly
contexualize VMs.
1. Start a VM with OS image on which you want to install ucloud-init
2. Execute the following command on the started VM
.. code-block:: sh
apk add git
git clone https://code.ungleich.ch/ucloud/ucloud-init.git
cd ucloud-init
sh ./install.sh
3. Congratulations. Your image is now ucloud-init ready.
Upload Sample OS Image
~~~~~~~~~~~~~~~~~~~~~~
Execute the following to get the sample OS image file.
.. code-block:: sh
mkdir /var/www/admin
(cd /var/www/admin && wget https://cloud.ungleich.ch/s/qTb5dFYW5ii8KsD/download)
Run File Scanner and Image Scanner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Currently, our uploaded file *alpine-untouched.qcow2* is not tracked by ucloud. We can only make
images from tracked files. So, we need to track the file by running File Scanner
Let's assume we have uploaded an *alpine-uploaded.qcow2* disk images to our
uncloud server. Currently, our *alpine-untouched.qcow2* is not tracked by
ucloud. We can only make images from tracked files. So, we need to track the
file by running File Scanner
.. code-block:: sh

View File

@ -0,0 +1,36 @@
Hacking
=======
Using uncloud in hacking (aka development) mode.
Get the code
------------
.. code-block:: sh
:linenos:
git clone https://code.ungleich.ch/uncloud/uncloud.git
Install python requirements
---------------------------
You need to have python3 installed.
.. code-block:: sh
:linenos:
cd uncloud!
python -m venv venv
. ./venv/bin/activate
./bin/uncloud-run-reinstall
Install os requirements
-----------------------
Install the following software packages: **dnsmasq**.
If you already have a working IPv6 SLAAC and DNS setup,
this step can be skipped.
Note that you need at least one /64 IPv6 network to run uncloud.

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -11,14 +11,13 @@ Welcome to ucloud's documentation!
:caption: Contents:
introduction
user-guide
setup-install
vm-images
user-guide
admin-guide
user-guide/how-to-create-an-os-image-for-ucloud
troubleshooting
hacking
Indices and tables
==================

View File

@ -0,0 +1,66 @@
VM images
==================================
Overview
---------
ucloud tries to be least invasise towards VMs and only require
strictly necessary changes for running in a virtualised
environment. This includes configurations for:
* Configuring the network
* Managing access via ssh keys
* Resizing the attached disk(s)
Upstream images
---------------
The 'official' uncloud images are defined in the `uncloud/images
<https://code.ungleich.ch/uncloud/images>`_ repository.
How to make you own Uncloud images
----------------------------------
.. note::
It is fairly easy to create your own images for uncloud, as the common
operations (which are detailed below) can be automatically handled by the
`uncloud/uncloud-init <https://code.ungleich.ch/uncloud/uncloud-init>`_ tool.
Network configuration
~~~~~~~~~~~~~~~~~~~~~
All VMs in ucloud are required to support IPv6. The primary network
configuration is always done using SLAAC. A VM thus needs only to be
configured to
* accept router advertisements on all network interfaces
* use the router advertisements to configure the network interfaces
* accept the DNS entries from the router advertisements
Configuring SSH keys
~~~~~~~~~~~~~~~~~~~~
To be able to access the VM, ucloud support provisioning SSH keys.
To accept ssh keys in your VM, request the URL
*http://metadata/ssh_keys*. Add the content to the appropriate user's
**authorized_keys** file. Below you find sample code to accomplish
this task:
.. code-block:: sh
tmp=$(mktemp)
curl -s http://metadata/ssk_keys > "$tmp"
touch ~/.ssh/authorized_keys # ensure it exists
cat ~/.ssh/authorized_keys >> "$tmp"
sort "$tmp" | uniq > ~/.ssh/authorized_keys
Disk resize
~~~~~~~~~~~
In virtualised environments, the disk sizes might grow. The operating
system should detect disks that are bigger than the existing partition
table and resize accordingly. This task is os specific.
ucloud does not support shrinking disks due to the complexity and
intra OS dependencies.

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
import logging
import sys
import importlib
import argparse
import os
from etcd3.exceptions import ConnectionFailedError
from uncloud.common import settings
from uncloud import UncloudException
from uncloud.common.cli import resolve_otp_credentials
# Components that use etcd
ETCD_COMPONENTS = ['api', 'scheduler', 'host', 'filescanner',
'imagescanner', 'metadata', 'configure', 'hack']
ALL_COMPONENTS = ETCD_COMPONENTS.copy()
ALL_COMPONENTS.append('oneshot')
#ALL_COMPONENTS.append('cli')
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser()
subparsers = arg_parser.add_subparsers(dest='command')
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--debug', '-d', action='store_true', default=False,
help='More verbose logging')
parent_parser.add_argument('--conf-dir', '-c', help='Configuration directory',
default=os.path.expanduser('~/uncloud'))
etcd_parser = argparse.ArgumentParser(add_help=False)
etcd_parser.add_argument('--etcd-host')
etcd_parser.add_argument('--etcd-port')
etcd_parser.add_argument('--etcd-ca-cert', help='CA that signed the etcd certificate')
etcd_parser.add_argument('--etcd-cert-cert', help='Path to client certificate')
etcd_parser.add_argument('--etcd-cert-key', help='Path to client certificate key')
for component in ALL_COMPONENTS:
mod = importlib.import_module('uncloud.{}.main'.format(component))
parser = getattr(mod, 'arg_parser')
if component in ETCD_COMPONENTS:
subparsers.add_parser(name=parser.prog, parents=[parser, parent_parser, etcd_parser])
else:
subparsers.add_parser(name=parser.prog, parents=[parser, parent_parser])
arguments = vars(arg_parser.parse_args())
etcd_arguments = [key for key, value in arguments.items() if key.startswith('etcd_') and value]
etcd_arguments = {
'etcd': {
key.replace('etcd_', ''): arguments[key]
for key in etcd_arguments
}
}
if not arguments['command']:
arg_parser.print_help()
else:
# Initializing Settings and resolving otp_credentials
# It is neccessary to resolve_otp_credentials after argument parsing is done because
# previously we were reading config file which was fixed to ~/uncloud/uncloud.conf and
# providing the default values for --name, --realm and --seed arguments from the values
# we read from file. But, now we are asking user about where the config file lives. So,
# to providing default value is not possible before parsing arguments. So, we are doing
# it after..
# settings.settings = settings.Settings(arguments['conf_dir'], seed_value=etcd_arguments)
# resolve_otp_credentials(arguments)
name = arguments.pop('command')
mod = importlib.import_module('uncloud.{}.main'.format(name))
main = getattr(mod, 'main')
if arguments['debug']:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
log = logging.getLogger()
try:
main(arguments)
except UncloudException as err:
log.error(err)
sys.exit(1)
# except ConnectionFailedError as err:
# log.error('Cannot connect to etcd: {}'.format(err))
except Exception as err:
log.exception(err)

View File

@ -40,7 +40,8 @@ setup(
"pynetbox",
"colorama",
"etcd3 @ https://github.com/kragniz/python-etcd3/tarball/master#egg=etcd3",
"marshmallow"
"marshmallow",
"ldap3"
],
scripts=["scripts/uncloud"],
data_files=[

View File

@ -0,0 +1,37 @@
import unittest
from unittest.mock import Mock
from uncloud.hack.mac import MAC
from uncloud import UncloudException
class TestMacLocal(unittest.TestCase):
def setUp(self):
self.config = Mock()
self.config.arguments = {"no_db":True}
self.mac = MAC(self.config)
self.mac.create()
def testMacInt(self):
self.assertEqual(self.mac.__int__(), int("0x420000000001",0), "wrong first MAC index")
def testMacRepr(self):
self.assertEqual(self.mac.__repr__(), '420000000001', "wrong first MAC index")
def testMacStr(self):
self.assertEqual(self.mac.__str__(), '42:00:00:00:00:01', "wrong first MAC index")
def testValidationRaise(self):
with self.assertRaises(UncloudException):
self.mac.validate_mac("2")
def testValidation(self):
self.assertTrue(self.mac.validate_mac("42:00:00:00:00:01"), "Validation of a given MAC not working properly")
def testNextMAC(self):
self.mac.create()
self.assertEqual(self.mac.__repr__(), '420000000001', "wrong second MAC index")
self.assertEqual(self.mac.__int__(), int("0x420000000001",0), "wrong second MAC index")
self.assertEqual(self.mac.__str__(), '42:00:00:00:00:01', "wrong second MAC index")
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,2 @@
class UncloudException(Exception):
pass

View File

@ -1,7 +1,6 @@
import os
from uncloud.common.shared import shared
from uncloud.common.settings import settings
class Optional:
@ -54,9 +53,7 @@ class VmUUIDField(Field):
def vm_uuid_validation(self):
r = shared.etcd_client.get(
os.path.join(settings["etcd"]["vm_prefix"], self.uuid)
os.path.join(shared.settings["etcd"]["vm_prefix"], self.uuid)
)
if not r:
self.add_error(
"VM with uuid {} does not exists".format(self.uuid)
)
self.add_error("VM with uuid {} does not exists".format(self.uuid))

View File

@ -4,7 +4,6 @@ import os
from uuid import uuid4
from uncloud.common.shared import shared
from uncloud.common.settings import settings
data = {
'is_public': True,
@ -15,6 +14,6 @@ data = {
}
shared.etcd_client.put(
os.path.join(settings['etcd']['image_store_prefix'], uuid4().hex),
os.path.join(shared.settings['etcd']['image_store_prefix'], uuid4().hex),
json.dumps(data),
)

View File

@ -7,7 +7,6 @@ import requests
from pyotp import TOTP
from uncloud.common.shared import shared
from uncloud.common.settings import settings
logger = logging.getLogger(__name__)
@ -15,9 +14,9 @@ logger = logging.getLogger(__name__)
def check_otp(name, realm, token):
try:
data = {
"auth_name": settings["otp"]["auth_name"],
"auth_token": TOTP(settings["otp"]["auth_seed"]).now(),
"auth_realm": settings["otp"]["auth_realm"],
"auth_name": shared.settings["otp"]["auth_name"],
"auth_token": TOTP(shared.settings["otp"]["auth_seed"]).now(),
"auth_realm": shared.settings["otp"]["auth_realm"],
"name": name,
"realm": realm,
"token": token,
@ -25,13 +24,13 @@ def check_otp(name, realm, token):
except binascii.Error as err:
logger.error(
"Cannot compute OTP for seed: {}".format(
settings["otp"]["auth_seed"]
shared.settings["otp"]["auth_seed"]
)
)
return 400
response = requests.post(
settings["otp"]["verification_controller_url"], json=data
shared.settings["otp"]["verification_controller_url"], json=data
)
return response.status_code
@ -87,7 +86,7 @@ def resolve_image_name(name, etcd_client):
)
images = etcd_client.get_prefix(
settings["etcd"]["image_prefix"], value_in_json=True
shared.settings["etcd"]["image_prefix"], value_in_json=True
)
# Try to find image with name == image_name and store_name == store_name
@ -111,9 +110,7 @@ def random_bytes(num=6):
return [random.randrange(256) for _ in range(num)]
def generate_mac(
uaa=False, multicast=False, oui=None, separator=":", byte_fmt="%02x"
):
def generate_mac(uaa=False, multicast=False, oui=None, separator=":", byte_fmt="%02x"):
mac = random_bytes()
if oui:
if type(oui) == str:
@ -148,3 +145,4 @@ def mac2ipv6(mac, prefix):
lower_part = ipaddress.IPv6Address(":".join(ipv6_parts))
prefix = ipaddress.IPv6Address(prefix)
return str(prefix + int(lower_part))

View File

@ -15,9 +15,8 @@ from uncloud.common.shared import shared
from uncloud.common import counters
from uncloud.common.vm import VMStatus
from uncloud.common.request import RequestEntry, RequestType
from uncloud.common.settings import settings
from . import schemas
from .helper import generate_mac, mac2ipv6
from uncloud.api import schemas
from uncloud.api.helper import generate_mac, mac2ipv6
from uncloud import UncloudException
logger = logging.getLogger(__name__)
@ -50,7 +49,7 @@ class CreateVM(Resource):
validator = schemas.CreateVMSchema(data)
if validator.is_valid():
vm_uuid = uuid4().hex
vm_key = join_path(settings['etcd']['vm_prefix'], vm_uuid)
vm_key = join_path(shared.settings['etcd']['vm_prefix'], vm_uuid)
specs = {
'cpu': validator.specs['cpu'],
'ram': validator.specs['ram'],
@ -60,7 +59,7 @@ class CreateVM(Resource):
macs = [generate_mac() for _ in range(len(data['network']))]
tap_ids = [
counters.increment_etcd_counter(
shared.etcd_client, settings['etcd']['tap_counter']
shared.etcd_client, shared.settings['etcd']['tap_counter']
)
for _ in range(len(data['network']))
]
@ -84,7 +83,7 @@ class CreateVM(Resource):
r = RequestEntry.from_scratch(
type=RequestType.ScheduleVM,
uuid=vm_uuid,
request_prefix=settings['etcd']['request_prefix'],
request_prefix=shared.settings['etcd']['request_prefix'],
)
shared.request_pool.put(r)
@ -99,7 +98,7 @@ class VmStatus(Resource):
validator = schemas.VMStatusSchema(data)
if validator.is_valid():
vm = shared.vm_pool.get(
join_path(settings['etcd']['vm_prefix'], data['uuid'])
join_path(shared.settings['etcd']['vm_prefix'], data['uuid'])
)
vm_value = vm.value.copy()
vm_value['ip'] = []
@ -107,7 +106,7 @@ class VmStatus(Resource):
network_name, mac, tap = network_mac_and_tap
network = shared.etcd_client.get(
join_path(
settings['etcd']['network_prefix'],
shared.settings['etcd']['network_prefix'],
data['name'],
network_name,
),
@ -130,7 +129,7 @@ class CreateImage(Resource):
validator = schemas.CreateImageSchema(data)
if validator.is_valid():
file_entry = shared.etcd_client.get(
join_path(settings['etcd']['file_prefix'], data['uuid'])
join_path(shared.settings['etcd']['file_prefix'], data['uuid'])
)
file_entry_value = json.loads(file_entry.value)
@ -144,7 +143,7 @@ class CreateImage(Resource):
}
shared.etcd_client.put(
join_path(
settings['etcd']['image_prefix'], data['uuid']
shared.settings['etcd']['image_prefix'], data['uuid']
),
json.dumps(image_entry_json),
)
@ -157,7 +156,7 @@ class ListPublicImages(Resource):
@staticmethod
def get():
images = shared.etcd_client.get_prefix(
settings['etcd']['image_prefix'], value_in_json=True
shared.settings['etcd']['image_prefix'], value_in_json=True
)
r = {'images': []}
for image in images:
@ -178,7 +177,7 @@ class VMAction(Resource):
if validator.is_valid():
vm_entry = shared.vm_pool.get(
join_path(settings['etcd']['vm_prefix'], data['uuid'])
join_path(shared.settings['etcd']['vm_prefix'], data['uuid'])
)
action = data['action']
@ -208,7 +207,7 @@ class VMAction(Resource):
type='{}VM'.format(action.title()),
uuid=data['uuid'],
hostname=vm_entry.hostname,
request_prefix=settings['etcd']['request_prefix'],
request_prefix=shared.settings['etcd']['request_prefix'],
)
shared.request_pool.put(r)
return (
@ -231,10 +230,10 @@ class VMMigration(Resource):
type=RequestType.InitVMMigration,
uuid=vm.uuid,
hostname=join_path(
settings['etcd']['host_prefix'],
shared.settings['etcd']['host_prefix'],
validator.destination.value,
),
request_prefix=settings['etcd']['request_prefix'],
request_prefix=shared.settings['etcd']['request_prefix'],
)
shared.request_pool.put(r)
@ -254,7 +253,7 @@ class ListUserVM(Resource):
if validator.is_valid():
vms = shared.etcd_client.get_prefix(
settings['etcd']['vm_prefix'], value_in_json=True
shared.settings['etcd']['vm_prefix'], value_in_json=True
)
return_vms = []
user_vms = filter(
@ -287,7 +286,7 @@ class ListUserFiles(Resource):
if validator.is_valid():
files = shared.etcd_client.get_prefix(
settings['etcd']['file_prefix'], value_in_json=True
shared.settings['etcd']['file_prefix'], value_in_json=True
)
return_files = []
user_files = [f for f in files if f.value['owner'] == data['name']]
@ -312,7 +311,7 @@ class CreateHost(Resource):
validator = schemas.CreateHostSchema(data)
if validator.is_valid():
host_key = join_path(
settings['etcd']['host_prefix'], uuid4().hex
shared.settings['etcd']['host_prefix'], uuid4().hex
)
host_entry = {
'specs': data['specs'],
@ -354,7 +353,7 @@ class GetSSHKeys(Resource):
# {user_prefix}/{realm}/{name}/key/
etcd_key = join_path(
settings['etcd']['user_prefix'],
shared.settings['etcd']['user_prefix'],
data['realm'],
data['name'],
'key',
@ -372,7 +371,7 @@ class GetSSHKeys(Resource):
# {user_prefix}/{realm}/{name}/key/{key_name}
etcd_key = join_path(
settings['etcd']['user_prefix'],
shared.settings['etcd']['user_prefix'],
data['realm'],
data['name'],
'key',
@ -405,7 +404,7 @@ class AddSSHKey(Resource):
# {user_prefix}/{realm}/{name}/key/{key_name}
etcd_key = join_path(
settings['etcd']['user_prefix'],
shared.settings['etcd']['user_prefix'],
data['realm'],
data['name'],
'key',
@ -439,7 +438,7 @@ class RemoveSSHKey(Resource):
# {user_prefix}/{realm}/{name}/key/{key_name}
etcd_key = join_path(
settings['etcd']['user_prefix'],
shared.settings['etcd']['user_prefix'],
data['realm'],
data['name'],
'key',
@ -471,23 +470,23 @@ class CreateNetwork(Resource):
network_entry = {
'id': counters.increment_etcd_counter(
shared.etcd_client, settings['etcd']['vxlan_counter']
shared.etcd_client, shared.settings['etcd']['vxlan_counter']
),
'type': data['type'],
}
if validator.user.value:
try:
nb = pynetbox.api(
url=settings['netbox']['url'],
token=settings['netbox']['token'],
url=shared.settings['netbox']['url'],
token=shared.settings['netbox']['token'],
)
nb_prefix = nb.ipam.prefixes.get(
prefix=settings['network']['prefix']
prefix=shared.settings['network']['prefix']
)
prefix = nb_prefix.available_prefixes.create(
data={
'prefix_length': int(
settings['network']['prefix_length']
shared.settings['network']['prefix_length']
),
'description': '{}\'s network "{}"'.format(
data['name'], data['network_name']
@ -506,7 +505,7 @@ class CreateNetwork(Resource):
network_entry['ipv6'] = 'fd00::/64'
network_key = join_path(
settings['etcd']['network_prefix'],
shared.settings['etcd']['network_prefix'],
data['name'],
data['network_name'],
)
@ -526,7 +525,7 @@ class ListUserNetwork(Resource):
if validator.is_valid():
prefix = join_path(
settings['etcd']['network_prefix'], data['name']
shared.settings['etcd']['network_prefix'], data['name']
)
networks = shared.etcd_client.get_prefix(
prefix, value_in_json=True
@ -563,11 +562,14 @@ api.add_resource(ListHost, '/host/list')
api.add_resource(CreateNetwork, '/network/create')
def main(debug=False, port=None):
def main(arguments):
debug = arguments['debug']
port = arguments['port']
try:
image_stores = list(
shared.etcd_client.get_prefix(
settings['etcd']['image_store_prefix'], value_in_json=True
shared.settings['etcd']['image_store_prefix'], value_in_json=True
)
)
except KeyError:
@ -587,18 +589,12 @@ def main(debug=False, port=None):
# shared.etcd_client.put(
# join_path(
# settings['etcd']['image_store_prefix'], uuid4().hex
# shared.settings['etcd']['image_store_prefix'], uuid4().hex
# ),
# json.dumps(data),
# )
try:
app.run(host='::',
port=port,
debug=debug)
app.run(host='::', port=port, debug=debug)
except OSError as e:
raise UncloudException('Failed to start Flask: {}'.format(e))
if __name__ == '__main__':
main()

View File

@ -22,7 +22,6 @@ import bitmath
from uncloud.common.host import HostStatus
from uncloud.common.vm import VMStatus
from uncloud.common.shared import shared
from uncloud.common.settings import settings
from . import helper, logger
from .common_fields import Field, VmUUIDField
from .helper import check_otp, resolve_vm_name
@ -112,7 +111,7 @@ class CreateImageSchema(BaseSchema):
def file_uuid_validation(self):
file_entry = shared.etcd_client.get(
os.path.join(
settings["etcd"]["file_prefix"], self.uuid.value
shared.shared.shared.shared.shared.settings["etcd"]["file_prefix"], self.uuid.value
)
)
if file_entry is None:
@ -125,7 +124,7 @@ class CreateImageSchema(BaseSchema):
def image_store_name_validation(self):
image_stores = list(
shared.etcd_client.get_prefix(
settings["etcd"]["image_store_prefix"]
shared.shared.shared.shared.shared.settings["etcd"]["image_store_prefix"]
)
)
@ -283,7 +282,7 @@ class CreateVMSchema(OTPSchema):
for net in _network:
network = shared.etcd_client.get(
os.path.join(
settings["etcd"]["network_prefix"],
shared.shared.shared.shared.shared.settings["etcd"]["network_prefix"],
self.name.value,
net,
),
@ -488,7 +487,7 @@ class VmMigrationSchema(OTPSchema):
self.add_error("Can't migrate non-running VM")
if vm.hostname == os.path.join(
settings["etcd"]["host_prefix"], self.destination.value
shared.shared.shared.shared.shared.settings["etcd"]["host_prefix"], self.destination.value
):
self.add_error(
"Destination host couldn't be same as Source Host"
@ -539,9 +538,7 @@ class CreateNetwork(OTPSchema):
super().__init__(data, fields=fields)
def network_name_validation(self):
print(self.name.value, self.network_name.value)
key = os.path.join(settings["etcd"]["network_prefix"], self.name.value, self.network_name.value)
print(key)
key = os.path.join(shared.shared.shared.shared.shared.settings["etcd"]["network_prefix"], self.name.value, self.network_name.value)
network = shared.etcd_client.get(key, value_in_json=True)
if network:
self.add_error(

View File

@ -5,15 +5,15 @@ import binascii
from pyotp import TOTP
from os.path import join as join_path
from uncloud.common.settings import settings
from uncloud.common.shared import shared
def get_otp_parser():
otp_parser = argparse.ArgumentParser('otp')
otp_parser.add_argument('--name', default=settings['client']['name'])
otp_parser.add_argument('--realm', default=settings['client']['realm'])
otp_parser.add_argument('--seed', type=get_token, default=settings['client']['seed'],
dest='token', metavar='SEED')
otp_parser.add_argument('--name')
otp_parser.add_argument('--realm')
otp_parser.add_argument('--seed', type=get_token, dest='token', metavar='SEED')
return otp_parser
@ -25,11 +25,15 @@ def load_dump_pretty(content):
def make_request(*args, data=None, request_method=requests.post):
r = request_method(join_path(settings['client']['api_server'], *args), json=data)
try:
print(load_dump_pretty(r.content))
except Exception:
print('Error occurred while getting output from api server.')
r = request_method(join_path(shared.settings['client']['api_server'], *args), json=data)
except requests.exceptions.RequestException:
print('Error occurred while connecting to API server.')
else:
try:
print(load_dump_pretty(r.content))
except Exception:
print('Error occurred while getting output from api server.')
def get_token(seed):

View File

@ -12,12 +12,12 @@ for component in ['user', 'host', 'image', 'network', 'vm']:
subparser.add_parser(name=parser.prog, parents=[parser])
def main(**kwargs):
if not kwargs['subcommand']:
def main(arguments):
if not arguments['subcommand']:
arg_parser.print_help()
else:
name = kwargs.pop('subcommand')
kwargs.pop('debug')
name = arguments.pop('subcommand')
arguments.pop('debug')
mod = importlib.import_module('uncloud.cli.{}'.format(name))
_main = getattr(mod, 'main')
_main(**kwargs)
_main(**arguments)

View File

@ -0,0 +1,23 @@
import argparse
import etcd3
from uncloud.common.etcd_wrapper import Etcd3Wrapper
arg_parser = argparse.ArgumentParser('client', add_help=False)
arg_parser.add_argument('--dump-etcd-contents-prefix', help="Dump contents below the given prefix")
def dump_etcd_contents(prefix):
etcd = Etcd3Wrapper()
for k,v in etcd.get_prefix_raw(prefix):
k = k.decode('utf-8')
v = v.decode('utf-8')
print("{} = {}".format(k,v))
# print("{} = {}".format(k,v))
# for k,v in etcd.get_prefix(prefix):
#
print("done")
def main(arguments):
if 'dump_etcd_contents_prefix' in arguments:
dump_etcd_contents(prefix=arguments['dump_etcd_contents_prefix'])

View File

@ -0,0 +1,26 @@
from uncloud.common.shared import shared
from pyotp import TOTP
def get_token(seed):
if seed is not None:
try:
token = TOTP(seed).now()
except Exception:
raise Exception('Invalid seed')
else:
return token
def resolve_otp_credentials(kwargs):
d = {
'name': shared.settings['client']['name'],
'realm': shared.settings['client']['realm'],
'token': get_token(shared.settings['client']['seed'])
}
for k, v in d.items():
if k in kwargs and kwargs[k] is None:
kwargs.update({k: v})
return d

View File

@ -61,33 +61,15 @@ class Etcd3Wrapper:
@readable_errors
def get_prefix(self, *args, value_in_json=False, raise_exception=True, **kwargs):
try:
event_iterator = self.client.get_prefix(*args, **kwargs)
for e in event_iterator:
yield EtcdEntry(*e[::-1], value_in_json=value_in_json)
except Exception as err:
if raise_exception:
raise Exception('Exception in etcd_wrapper.get_prefix') from err
else:
logger.exception('Error in etcd_wrapper')
return iter([])
event_iterator = self.client.get_prefix(*args, **kwargs)
for e in event_iterator:
yield EtcdEntry(*e[::-1], value_in_json=value_in_json)
@readable_errors
def watch_prefix(self, key, raise_exception=True, value_in_json=False):
try:
event_iterator, cancel = self.client.watch_prefix(key)
for e in event_iterator:
if hasattr(e, '_event'):
e = e._event
if e.type == e.PUT:
yield EtcdEntry(e.kv.key, e.kv.value, value_in_json=value_in_json)
except Exception as err:
if raise_exception:
raise Exception('Exception in etcd_wrapper.get_prefix') from err
else:
logger.exception('Error in etcd_wrapper.watch_prefix')
try:
cancel()
except Exception:
pass
return iter([])
event_iterator, cancel = self.client.watch_prefix(key)
for e in event_iterator:
if hasattr(e, '_event'):
e = e._event
if e.type == e.PUT:
yield EtcdEntry(e.kv.key, e.kv.value, value_in_json=value_in_json)

View File

@ -8,6 +8,7 @@ from uncloud.common.etcd_wrapper import Etcd3Wrapper
from os.path import join as join_path
logger = logging.getLogger(__name__)
settings = None
class CustomConfigParser(configparser.RawConfigParser):
@ -25,9 +26,8 @@ class CustomConfigParser(configparser.RawConfigParser):
class Settings(object):
def __init__(self):
def __init__(self, conf_dir, seed_value=None):
conf_name = 'uncloud.conf'
conf_dir = os.environ.get('UCLOUD_CONF_DIR', os.path.expanduser('~/uncloud/'))
self.config_file = join_path(conf_dir, conf_name)
# this is used to cache config from etcd for 1 minutes. Without this we
@ -38,15 +38,19 @@ class Settings(object):
self.config_parser.add_section('etcd')
self.config_parser.set('etcd', 'base_prefix', '/')
try:
if os.access(self.config_file, os.R_OK):
self.config_parser.read(self.config_file)
except Exception as err:
logger.error('%s', err)
else:
raise FileNotFoundError('Config file %s not found!', self.config_file)
self.config_key = join_path(self['etcd']['base_prefix'] + 'uncloud/config/')
self.read_internal_values()
if seed_value is None:
seed_value = dict()
self.config_parser.read_dict(seed_value)
def get_etcd_client(self):
args = tuple()
try:
@ -99,12 +103,10 @@ class Settings(object):
def read_config_file_values(self, config_file):
try:
# Trying to read configuration file
with open(config_file, 'r') as config_file_handle:
with open(config_file) as config_file_handle:
self.config_parser.read_file(config_file_handle)
except FileNotFoundError:
sys.exit(
'Configuration file {} not found!'.format(config_file)
)
sys.exit('Configuration file {} not found!'.format(config_file))
except Exception as err:
logger.exception(err)
sys.exit('Error occurred while reading configuration file')
@ -130,4 +132,5 @@ class Settings(object):
return self.config_parser[key]
settings = Settings()
def get_settings():
return settings

View File

@ -0,0 +1,34 @@
from uncloud.common.settings import get_settings
from uncloud.common.vm import VmPool
from uncloud.common.host import HostPool
from uncloud.common.request import RequestPool
import uncloud.common.storage_handlers as storage_handlers
class Shared:
@property
def settings(self):
return get_settings()
@property
def etcd_client(self):
return self.settings.get_etcd_client()
@property
def host_pool(self):
return HostPool(self.etcd_client, self.settings["etcd"]["host_prefix"])
@property
def vm_pool(self):
return VmPool(self.etcd_client, self.settings["etcd"]["vm_prefix"])
@property
def request_pool(self):
return RequestPool(self.etcd_client, self.settings["etcd"]["request_prefix"])
@property
def storage_handler(self):
return storage_handlers.get_storage_handler()
shared = Shared()

View File

@ -6,8 +6,7 @@ import stat
from abc import ABC
from . import logger
from os.path import join as join_path
from uncloud.common.settings import settings as config
import uncloud.common.shared as shared
class ImageStorageHandler(ABC):
@ -193,16 +192,16 @@ class CEPHBasedImageStorageHandler(ImageStorageHandler):
def get_storage_handler():
__storage_backend = config["storage"]["storage_backend"]
__storage_backend = shared.shared.settings["storage"]["storage_backend"]
if __storage_backend == "filesystem":
return FileSystemBasedImageStorageHandler(
vm_base=config["storage"]["vm_dir"],
image_base=config["storage"]["image_dir"],
vm_base=shared.shared.settings["storage"]["vm_dir"],
image_base=shared.shared.settings["storage"]["image_dir"],
)
elif __storage_backend == "ceph":
return CEPHBasedImageStorageHandler(
vm_base=config["storage"]["ceph_vm_pool"],
image_base=config["storage"]["ceph_image_pool"],
vm_base=shared.shared.settings["storage"]["ceph_vm_pool"],
image_base=shared.shared.settings["storage"]["ceph_image_pool"],
)
else:
raise Exception("Unknown Image Storage Handler")
raise Exception("Unknown Image Storage Handler")

View File

@ -1,7 +1,6 @@
import os
import argparse
from uncloud.common.settings import settings
from uncloud.common.shared import shared
arg_parser = argparse.ArgumentParser('configure', add_help=False)
@ -40,19 +39,19 @@ ceph_storage_parser.add_argument('--ceph-image-pool', required=True)
def update_config(section, kwargs):
uncloud_config = shared.etcd_client.get(settings.config_key, value_in_json=True)
uncloud_config = shared.etcd_client.get(shared.settings.config_key, value_in_json=True)
if not uncloud_config:
uncloud_config = {}
else:
uncloud_config = uncloud_config.value
uncloud_config[section] = kwargs
shared.etcd_client.put(settings.config_key, uncloud_config, value_in_json=True)
shared.etcd_client.put(shared.settings.config_key, uncloud_config, value_in_json=True)
def main(**kwargs):
subcommand = kwargs.pop('subcommand')
def main(arguments):
subcommand = arguments['subcommand']
if not subcommand:
arg_parser.print_help()
else:
update_config(subcommand, kwargs)
update_config(subcommand, arguments)

View File

@ -9,7 +9,6 @@ import bitmath
from uuid import uuid4
from . import logger
from uncloud.common.settings import settings
from uncloud.common.shared import shared
arg_parser = argparse.ArgumentParser('filescanner', add_help=False)
@ -53,7 +52,7 @@ def track_file(file, base_dir, host):
file_path = file_path.relative_to(owner)
creation_date = time.ctime(os.stat(file_str).st_ctime)
entry_key = os.path.join(settings['etcd']['file_prefix'], str(uuid4()))
entry_key = os.path.join(shared.settings['etcd']['file_prefix'], str(uuid4()))
entry_value = {
'filename': str(file_path),
'owner': owner,
@ -68,8 +67,9 @@ def track_file(file, base_dir, host):
shared.etcd_client.put(entry_key, entry_value, value_in_json=True)
def main(hostname, debug=False):
base_dir = settings['storage']['file_dir']
def main(arguments):
hostname = arguments['hostname']
base_dir = shared.settings['storage']['file_dir']
# Recursively Get All Files and Folder below BASE_DIR
files = glob.glob('{}/**'.format(base_dir), recursive=True)
files = [pathlib.Path(f) for f in files if pathlib.Path(f).is_file()]
@ -77,7 +77,7 @@ def main(hostname, debug=False):
# Files that are already tracked
tracked_files = [
pathlib.Path(os.path.join(base_dir, f.value['owner'], f.value['filename']))
for f in shared.etcd_client.get_prefix(settings['etcd']['file_prefix'], value_in_json=True)
for f in shared.etcd_client.get_prefix(shared.settings['etcd']['file_prefix'], value_in_json=True)
if f.value['host'] == hostname
]
untracked_files = set(files) - set(tracked_files)

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# 2020 Nico Schottelius (nico.schottelius at ungleich.ch)
#
# This file is part of uncloud.
#
# uncloud is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# uncloud is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with uncloud. If not, see <http://www.gnu.org/licenses/>.
#
#
class Config(object):
def __init__(self, arguments):
""" read arguments dicts as a base """
self.arguments = arguments
# Split them so *etcd_args can be used and we can
# iterate over etcd_hosts
self.etcd_hosts = [ arguments['etcd_host'] ]
self.etcd_args = {
'ca_cert': arguments['etcd_ca_cert'],
'cert_cert': arguments['etcd_cert_cert'],
'cert_key': arguments['etcd_cert_key'],
# 'user': None,
# 'password': None
}
self.etcd_prefix = '/nicohack/'

View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# 2020 Nico Schottelius (nico.schottelius at ungleich.ch)
#
# This file is part of uncloud.
#
# uncloud is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# uncloud is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with uncloud. If not, see <http://www.gnu.org/licenses/>.
#
#
import etcd3
import json
import logging
import datetime
import re
from functools import wraps
from uncloud import UncloudException
log = logging.getLogger(__name__)
def db_logentry(message):
timestamp = datetime.datetime.now()
return {
"timestamp": str(timestamp),
"message": message
}
def readable_errors(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except etcd3.exceptions.ConnectionFailedError as e:
raise UncloudException('Cannot connect to etcd: is etcd running and reachable? {}'.format(e))
except etcd3.exceptions.ConnectionTimeoutError as e:
raise UncloudException('etcd connection timeout. {}'.format(e))
return wrapper
class DB(object):
def __init__(self, config, prefix="/"):
self.config = config
# Root for everything
self.base_prefix= '/nicohack'
# Can be set from outside
self.prefix = prefix
try:
self.connect()
except FileNotFoundError as e:
raise UncloudException("Is the path to the etcd certs correct? {}".format(e))
@readable_errors
def connect(self):
self._db_clients = []
for endpoint in self.config.etcd_hosts:
client = etcd3.client(host=endpoint, **self.config.etcd_args)
self._db_clients.append(client)
def realkey(self, key):
return "{}{}/{}".format(self.base_prefix,
self.prefix,
key)
@readable_errors
def get(self, key, as_json=False, **kwargs):
value, _ = self._db_clients[0].get(self.realkey(key), **kwargs)
if as_json:
value = json.loads(value)
return value
@readable_errors
def get_prefix(self, key, as_json=False, **kwargs):
for value, meta in self._db_clients[0].get_prefix(self.realkey(key), **kwargs):
k = meta.key.decode("utf-8")
value = value.decode("utf-8")
if as_json:
value = json.loads(value)
yield (k, value)
@readable_errors
def set(self, key, value, as_json=False, **kwargs):
if as_json:
value = json.dumps(value)
log.debug("Setting {} = {}".format(self.realkey(key), value))
# FIXME: iterate over clients in case of failure ?
return self._db_clients[0].put(self.realkey(key), value, **kwargs)
@readable_errors
def list_and_filter(self, key, filter_key=None, filter_regexp=None):
for k,v in self.get_prefix(key, as_json=True):
if filter_key and filter_regexp:
if filter_key in v:
if re.match(filter_regexp, v[filter_key]):
yield v
else:
yield v
@readable_errors
def increment(self, key, **kwargs):
print(self.realkey(key))
print("prelock")
lock = self._db_clients[0].lock('/nicohack/foo')
print("prelockacq")
lock.acquire()
print("prelockrelease")
lock.release()
with self._db_clients[0].lock("/nicohack/mac/last_used_index") as lock:
print("in lock")
pass
# with self._db_clients[0].lock(self.realkey(key)) as lock:# value = int(self.get(self.realkey(key), **kwargs))
# self.set(self.realkey(key), str(value + 1), **kwargs)
if __name__ == '__main__':
endpoints = [ "https://etcd1.ungleich.ch:2379",
"https://etcd2.ungleich.ch:2379",
"https://etcd3.ungleich.ch:2379" ]
db = DB(url=endpoints)

View File

@ -0,0 +1,3 @@
*.iso
radvdpid
foo

View File

@ -0,0 +1,6 @@
#!/bin/sh
etcdctl --cert=$HOME/vcs/ungleich-dot-cdist/files/etcd/nico.pem \
--key=/home/nico/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem \
--cacert=$HOME/vcs/ungleich-dot-cdist/files/etcd/ca.pem \
--endpoints https://etcd1.ungleich.ch:2379,https://etcd2.ungleich.ch:2379,https://etcd3.ungleich.ch:2379 "$@"

View File

@ -0,0 +1,3 @@
#!/bin/sh
echo $@

View File

@ -0,0 +1,7 @@
#!/bin/sh
dev=$1; shift
# bridge is setup from outside
ip link set dev "$dev" master ${bridge}
ip link set dev "$dev" up

View File

@ -0,0 +1 @@
000000000252

View File

@ -0,0 +1 @@
02:00

Some files were not shown because too many files have changed in this diff Show More