Compare commits
	
		
			754 Commits
		
	
	
		
			logs
			...
			443546027b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					443546027b | ||
| 
						 | 
					1c4f3f756f | ||
| 
						 | 
					3971d5ca5d | ||
| 
						 | 
					e95d1aa53b | ||
| 
						 | 
					1ab2bd2153 | ||
| 
						 | 
					d35ad440fa | ||
| 
						 | 
					d58dc56024 | ||
| 
						 | 
					34b7cdcf06 | ||
| 
						 | 
					af0d7807bc | ||
| 
						 | 
					e600fedcab | ||
| 
						 | 
					147c7bc3a1 | ||
| 
						 | 
					3fdf5c3ebf | ||
| 
						 | 
					cd177bd779 | ||
| 
						 | 
					2c8dcbe93d | ||
| 
						 | 
					e84d262f38 | ||
| 
						 | 
					29b192211d | ||
| 
						 | 
					583ca2fbac | ||
| 
						 | 
					82d25b0bee | ||
| 
						 | 
					181b3249b8 | ||
| 
						 | 
					8b38249df7 | ||
| 
						 | 
					01af8237db | ||
| 
						 | 
					2f4884c655 | ||
| 
						 | 
					c9ee2a1d24 | ||
| 
						 | 
					8d5ba6a5e4 | ||
| 
						 | 
					d3cfe019e3 | ||
| 
						 | 
					4c2ecd3f41 | ||
| 
						 | 
					d8ccdec501 | ||
| 
						 | 
					938f9f1326 | ||
| 
						 | 
					29bc21735d | ||
| 
						 | 
					ec7a7e4746 | ||
| 
						 | 
					0b0952b28c | ||
| 
						 | 
					9e52663261 | ||
| 
						 | 
					8f2adb76e4 | ||
| 
						 | 
					0d6c329477 | ||
| 
						 | 
					1c751f7253 | ||
| 
						 | 
					2264d22c69 | ||
| 
						 | 
					9fe72ea96e | ||
| 
						 | 
					48299810e0 | ||
| 
						 | 
					2a0ab8e549 | ||
| 
						 | 
					23a9d648d2 | ||
| 
						 | 
					a3029fa3f9 | ||
| 
						 | 
					387785b40c | ||
| 
						 | 
					03dea55131 | ||
| 
						 | 
					7b8aa989f6 | ||
| 
						 | 
					6ab6383144 | ||
| 
						 | 
					690d60f9d6 | ||
| 
						 | 
					da0de80afd | ||
| 
						 | 
					cd7ae788b1 | ||
| 
						 | 
					0d96cc53bf | ||
| 
						 | 
					66fc3c5b35 | ||
| 
						 | 
					5ab3eb8a38 | ||
| 
						 | 
					fec23b4acd | ||
| 
						 | 
					901622fee0 | ||
| 
						 | 
					527e622774 | ||
| 
						 | 
					7223b79fe8 | ||
| 
						 | 
					1ade41aeae | ||
| 
						 | 
					58dc579255 | ||
| 
						 | 
					370dac201b | ||
| 
						 | 
					2a763006db | ||
| 
						 | 
					522c66653b | ||
| 
						 | 
					b57f050b81 | ||
| 
						 | 
					41ebcf150a | ||
| 
						 | 
					1499def6ad | ||
| 
						 | 
					adbab0f5d7 | ||
| 88c88cac5b | |||
| 1ae38c98ad | |||
| 2d517cc594 | |||
| a9c82bd261 | |||
| 79aec86f5f | |||
| 9b3dfc7576 | |||
| 037ae74782 | |||
| b81c60a3ce | |||
| 363ac94c47 | |||
| 378f9e5095 | |||
| 659b494ee4 | |||
| 92965c6af2 | |||
| 70cb5aec9f | |||
| d59e77d5a2 | |||
| ff1b857ab0 | |||
| dbdccdb920 | |||
| fd3fef72d3 | |||
| 1890fd4f71 | |||
| 95af3cb515 | |||
| 3acebc451e | |||
| 
						 | 
					5111c9c8be | ||
| 
						 | 
					3ecb0e9d96 | ||
| 
						 | 
					b4a1766677 | ||
| 
						 | 
					241c6a5a08 | ||
| 
						 | 
					7c30633bde | ||
| 
						 | 
					81d3406305 | ||
| 
						 | 
					04f7537066 | ||
| 
						 | 
					6bf058ab5c | ||
| 
						 | 
					b771b5d25e | ||
| 
						 | 
					6e6ed4ea2c | ||
| 
						 | 
					a098f0a672 | ||
| 
						 | 
					cafadec146 | ||
| 
						 | 
					0940b63961 | ||
| 
						 | 
					a2dca94dca | ||
| 
						 | 
					085a8718e0 | ||
| 
						 | 
					271cc2caa0 | ||
| 
						 | 
					42b60ca5cd | ||
| 
						 | 
					4920322d0a | ||
| 
						 | 
					c7c1535ba9 | ||
| 
						 | 
					576f53f81b | ||
| 
						 | 
					c0e6247fb8 | ||
| 
						 | 
					3e85fdc779 | ||
| 
						 | 
					4833bcb710 | ||
| 
						 | 
					7d69d65dd2 | ||
| 
						 | 
					a098b3797a | ||
| 
						 | 
					7d03676ac2 | ||
| 
						 | 
					945b7a893e | ||
| 
						 | 
					ef028cb2b9 | ||
| 
						 | 
					4cfd0a1789 | ||
| 
						 | 
					7c57cf34a8 | ||
| 
						 | 
					019b590b4f | ||
| 
						 | 
					d82ae166a1 | ||
| 
						 | 
					ffaa67fb5d | ||
| 
						 | 
					a573a4ce71 | ||
| 
						 | 
					52d5a1fbf9 | ||
| 
						 | 
					4ad32401fd | ||
| 
						 | 
					f663ec80f5 | ||
| 
						 | 
					e55727d9e2 | ||
| 
						 | 
					4a178d01e3 | ||
| 
						 | 
					3d13833572 | ||
| 
						 | 
					31ec352b57 | ||
| 
						 | 
					940ef17f7b | ||
| 
						 | 
					ad3293da9d | ||
| 
						 | 
					3ffff7d32c | ||
| 
						 | 
					e646cfef0b | ||
| 
						 | 
					88b7cfe2fd | ||
| 
						 | 
					7201cabb43 | ||
| 
						 | 
					a8e2445c10 | ||
| 
						 | 
					69bf951866 | ||
| 
						 | 
					3061df4f13 | ||
| 
						 | 
					2ccb57ffb0 | ||
| 
						 | 
					847fce07bb | ||
| 
						 | 
					f481cde465 | ||
| 
						 | 
					bf114b39b7 | ||
| 
						 | 
					22d15fe395 | ||
| 
						 | 
					14977c7b2c | ||
| 
						 | 
					8d9bb20538 | ||
| 
						 | 
					6a977203ab | ||
| 
						 | 
					275bd56fe6 | ||
| 
						 | 
					2662709fed | ||
| 
						 | 
					64bea2a66e | ||
| 
						 | 
					6807614ac8 | ||
| 
						 | 
					676f2f4caa | ||
| 
						 | 
					a2f2d0ebef | ||
| 
						 | 
					b2113bff62 | ||
| 
						 | 
					892bd93471 | ||
| 
						 | 
					3ec0d554ed | ||
| 
						 | 
					976a5cedcb | ||
| 
						 | 
					107ce25801 | ||
| 
						 | 
					6350491f9f | ||
| 
						 | 
					c78f758202 | ||
| 
						 | 
					787c01b4be | ||
| 
						 | 
					826d7586b1 | ||
| 
						 | 
					84d20c52fa | ||
| 
						 | 
					b176874c2b | ||
| 
						 | 
					df2c38199c | ||
| 
						 | 
					ede2d5fd53 | ||
| 
						 | 
					d111a97521 | ||
| 
						 | 
					330768490a | ||
| 
						 | 
					74a1f66d26 | ||
| 
						 | 
					598774b0b1 | ||
| 
						 | 
					bf1d4a4001 | ||
| 
						 | 
					db85d1a48b | ||
| 
						 | 
					3ff7b47995 | ||
| 
						 | 
					8b03df7923 | ||
| 
						 | 
					98dc733240 | ||
| 
						 | 
					c02e3dffcf | ||
| 
						 | 
					1cdbcca7f7 | ||
| 
						 | 
					9b8acb83cb | ||
| 
						 | 
					7ca360be6a | ||
| 
						 | 
					6c4fab1adc | ||
| 
						 | 
					6551b1b97d | ||
| 
						 | 
					71d9bd4678 | ||
| 
						 | 
					1ad9ce09cb | ||
| 
						 | 
					d731277914 | ||
| 
						 | 
					24fe99cfa5 | ||
| 
						 | 
					2a373e7368 | ||
| 
						 | 
					68bacf5da4 | ||
| 
						 | 
					ed158ffdcb | ||
| 
						 | 
					fbb55e64dc | ||
| 
						 | 
					1521b8fac5 | ||
| 
						 | 
					97d466818a | ||
| 
						 | 
					c1888f8921 | ||
| 
						 | 
					db6049bab3 | ||
| 
						 | 
					5cc68bca6d | ||
| 
						 | 
					49e495f062 | ||
| 
						 | 
					1952d905d2 | ||
| 
						 | 
					2205ac9b58 | ||
| 
						 | 
					e9017767d1 | ||
| 
						 | 
					ad660b0ce8 | ||
| 
						 | 
					d15fdac27b | ||
| 
						 | 
					386881c283 | ||
| 
						 | 
					8cba10c4fe | ||
| 
						 | 
					f8ac3154e1 | ||
| 
						 | 
					df04133551 | ||
| 
						 | 
					99693d8ec0 | ||
| 
						 | 
					0e798dac50 | ||
| 
						 | 
					e6ac7d0da6 | ||
| 
						 | 
					9c71730d9c | ||
| 
						 | 
					4be954a6f3 | ||
| 
						 | 
					e9278111a6 | ||
| 
						 | 
					ed1e761052 | ||
| 
						 | 
					86b1e4ad5d | ||
| 
						 | 
					062c1afe85 | ||
| 
						 | 
					fa00980352 | ||
| 
						 | 
					2a93b17d71 | ||
| 
						 | 
					287aa3dea3 | ||
| 
						 | 
					8ab313e6cb | ||
| 
						 | 
					cccb54d38f | ||
| 
						 | 
					67940296d2 | ||
| 
						 | 
					67ebeca1f4 | ||
| 
						 | 
					b45e882559 | ||
| 
						 | 
					745bb58c59 | ||
| 
						 | 
					bf5a16f41b | ||
| 
						 | 
					bc12fb53be | ||
| 
						 | 
					0d83885b9b | ||
| 
						 | 
					de585a7234 | ||
| 
						 | 
					5c2980fb36 | ||
| 
						 | 
					e741a95cdb | ||
| 
						 | 
					19eb5239a6 | ||
| 
						 | 
					305f260503 | ||
| 
						 | 
					d1f6331ff8 | ||
| 
						 | 
					67b8215adf | ||
| 
						 | 
					58b36f2823 | ||
| 
						 | 
					2452d37acf | ||
| 
						 | 
					8e4ebbf622 | ||
| 
						 | 
					b85ca8674b | ||
| 
						 | 
					c63a1fef6c | ||
| 
						 | 
					66196da877 | ||
| 
						 | 
					e5c7dbe4cb | ||
| 
						 | 
					f72ceecc19 | ||
| 
						 | 
					ed787683f4 | ||
| 
						 | 
					d44fb976e4 | ||
| 
						 | 
					fb1b44e1d1 | ||
| 
						 | 
					d00109daf3 | ||
| 
						 | 
					367613a9d5 | ||
| 
						 | 
					b990fe42d3 | ||
| 
						 | 
					7d11c23eba | ||
| 
						 | 
					450fab437c | ||
| 
						 | 
					a4a249bab8 | ||
| 
						 | 
					d9c9f05cd2 | ||
| 
						 | 
					68f4189283 | ||
| 
						 | 
					0e0540af43 | ||
| 
						 | 
					555c5acb26 | ||
| 
						 | 
					b48e2cb3e5 | ||
| 
						 | 
					cf1c5f2186 | ||
| 
						 | 
					be38030395 | ||
| 
						 | 
					ad69c04951 | ||
| 
						 | 
					abd6c1d712 | ||
| 
						 | 
					a55f4c449c | ||
| 
						 | 
					1a4694c891 | ||
| 
						 | 
					b782248da7 | ||
| 
						 | 
					ae9a80c8f3 | ||
| 
						 | 
					918006302b | ||
| 
						 | 
					f30076e0f5 | ||
| 
						 | 
					78157b80d2 | ||
| 
						 | 
					4309309bc9 | ||
| 
						 | 
					0feab329c1 | ||
| 
						 | 
					1c32cd2d12 | ||
| 
						 | 
					a0f436b3e1 | ||
| 
						 | 
					6e5c873796 | ||
| 
						 | 
					3cdb43074b | ||
| 
						 | 
					11905339bb | ||
| 
						 | 
					301ef8dc05 | ||
| 
						 | 
					0e84db61de | ||
| 
						 | 
					21a7ff9010 | ||
| 5255ffc2f7 | |||
| fd1c579ec4 | |||
| 0f4adeea86 | |||
| 
						 | 
					be3b09b683 | ||
| 
						 | 
					7696f065f8 | ||
| 245f3adea3 | |||
| 21d08204b5 | |||
| 
						 | 
					02d1e93c78 | ||
| 1de4888599 | |||
| 
						 | 
					fbbce7817b | ||
| 
						 | 
					1fcbc7c08a | ||
| 
						 | 
					fd01f535a1 | ||
| 
						 | 
					6681c455d8 | ||
| 
						 | 
					4b88da8ff6 | ||
| 
						 | 
					ea55c94c73 | ||
| 
						 | 
					471e0c9d9b | ||
| 
						 | 
					2924ccd23b | ||
| 
						 | 
					6042d47700 | ||
| 
						 | 
					599a614480 | ||
| 
						 | 
					e2ddd7e4e6 | ||
| 
						 | 
					9a2ed2351d | ||
| 
						 | 
					2ec6899a18 | ||
| 
						 | 
					f17f48921d | ||
| 
						 | 
					cb21db672b | ||
| 
						 | 
					b591533ba4 | ||
| 
						 | 
					8d701e67d8 | ||
| 
						 | 
					8a11805fe8 | ||
| 
						 | 
					68bb4a51f1 | ||
| 
						 | 
					30be88c8ce | ||
| 
						 | 
					e524c865ea | ||
| 
						 | 
					9ea342c4c4 | ||
| 
						 | 
					b388e43f30 | ||
| 
						 | 
					e560f34215 | ||
| 
						 | 
					11a3767255 | ||
| 
						 | 
					0349ad9a29 | ||
| 
						 | 
					195f5b0794 | ||
| 
						 | 
					2816e3ea35 | ||
| 
						 | 
					15ca06aba8 | ||
| 
						 | 
					e9b3a65a0e | ||
| 
						 | 
					d8fac883d2 | ||
| 
						 | 
					ac49d3324d | ||
| 
						 | 
					3b77c0da83 | ||
| 
						 | 
					57f18b2244 | ||
| 
						 | 
					b49685aa82 | ||
| 
						 | 
					08e9ee67fe | ||
| 
						 | 
					94803f820a | ||
| 
						 | 
					898700d127 | ||
| 
						 | 
					cfcad2343d | ||
| 
						 | 
					aab0d54dfa | ||
| 
						 | 
					603641a589 | ||
| 
						 | 
					35d90f9c4e | ||
| 
						 | 
					3b8079ca87 | ||
| 
						 | 
					902ad0ea71 | ||
| 
						 | 
					9a8625f8b4 | ||
| 
						 | 
					c24f2f26c4 | ||
| 
						 | 
					14fe694fd3 | ||
| 
						 | 
					06c3af5d4d | ||
| 
						 | 
					8a21a7c803 | ||
| 
						 | 
					6497e7dbdd | ||
| 
						 | 
					b5dba2458a | ||
| 
						 | 
					b2a6ac19cb | ||
| 
						 | 
					d6dba8e1f1 | ||
| 
						 | 
					bc79d54284 | ||
| 
						 | 
					7fd44a55cb | ||
| 
						 | 
					f3df1e42b9 | ||
| 
						 | 
					4c0c75be91 | ||
| 
						 | 
					e6ca520a88 | ||
| 
						 | 
					2d249f38ff | ||
| 
						 | 
					4a17d0c07d | ||
| 
						 | 
					1d6b9db5f9 | ||
| 
						 | 
					d249bcdf94 | ||
| 
						 | 
					66413e15bb | ||
| 
						 | 
					828efb4c98 | ||
| 
						 | 
					06e048165e | ||
| 
						 | 
					c47ba64d49 | ||
| 
						 | 
					e4860ff67e | ||
| 
						 | 
					5f05b73366 | ||
| 
						 | 
					20ce1f5ef3 | ||
| 
						 | 
					4de43a301c | ||
| 
						 | 
					1a55212378 | ||
| 
						 | 
					c34e5579fc | ||
| 
						 | 
					cc8fc2df21 | ||
| 
						 | 
					cd902c6688 | ||
| 
						 | 
					84deb17e37 | ||
| 
						 | 
					158d3aacc8 | ||
| 
						 | 
					55a25aba83 | ||
| 
						 | 
					719fa239e0 | ||
| 
						 | 
					72d5c64c2d | ||
| 
						 | 
					e45fefe883 | ||
| 
						 | 
					8e82b87fb3 | ||
| 
						 | 
					59a1b52242 | ||
| 
						 | 
					3c54f3d39e | ||
| 
						 | 
					56f81fb30e | ||
| 
						 | 
					73fce1d8fb | ||
| 
						 | 
					3e7f8513eb | ||
| 
						 | 
					1561d0c81e | ||
| 
						 | 
					847ef2e95c | ||
| 
						 | 
					dbcd9cf004 | ||
| 
						 | 
					9654d59fc0 | ||
| 
						 | 
					b432266486 | ||
| 
						 | 
					73602b6c3d | ||
| 
						 | 
					9f5e6d6018 | ||
| 
						 | 
					62705cc9b9 | ||
| 
						 | 
					69fe3f8d76 | ||
| 
						 | 
					556d711ab6 | ||
| 
						 | 
					17749c6c0b | ||
| 
						 | 
					84024a143e | ||
| 
						 | 
					ae49c40ea5 | ||
| 
						 | 
					b90ffbc4f0 | ||
| 
						 | 
					ba6ac86bff | ||
| 
						 | 
					5d9c922b26 | ||
| 
						 | 
					208f69ae7e | ||
| 
						 | 
					3c14c64f4a | ||
| 
						 | 
					3bbde50f96 | ||
| 
						 | 
					65668f5bee | ||
| 
						 | 
					03e066c297 | ||
| 
						 | 
					e69d5b3351 | ||
| 
						 | 
					a39f539426 | ||
| 
						 | 
					6724ff38fe | ||
| 
						 | 
					c09994fd84 | ||
| 
						 | 
					1d0b06ac4a | ||
| 
						 | 
					7b2da3ba94 | ||
| 
						 | 
					c5bdc96542 | ||
| 
						 | 
					e28b87ce7b | ||
| 
						 | 
					0efb852839 | ||
| 
						 | 
					6dfd6058ef | ||
| 
						 | 
					c083ce748c | ||
| 
						 | 
					3388fcc6f3 | ||
| 
						 | 
					91128313fc | ||
| 
						 | 
					34212e86a5 | ||
| 
						 | 
					2de218782f | ||
| 
						 | 
					1a061f2d1f | ||
| 
						 | 
					de0b910e09 | ||
| 
						 | 
					d865ee2766 | ||
| 
						 | 
					9d246ad180 | ||
| 
						 | 
					2797d97537 | ||
| 
						 | 
					a09a04e1a7 | ||
| 
						 | 
					3f610619b5 | ||
| 
						 | 
					c309d97623 | ||
| 
						 | 
					93903b4938 | ||
| 
						 | 
					3c1a84011e | ||
| 
						 | 
					f9bfafa004 | ||
| 
						 | 
					f26249ab8b | ||
| 
						 | 
					4ec32bafa7 | ||
| 
						 | 
					e621fb616a | ||
| 
						 | 
					aaef5334ce | ||
| 
						 | 
					be976bd8ed | ||
| 
						 | 
					5f735b6e9d | ||
| 
						 | 
					f490dc3555 | ||
| 
						 | 
					cdf513c2c4 | ||
| 
						 | 
					eba6fd4f97 | ||
| 
						 | 
					1582654a9c | ||
| 
						 | 
					0ac55a0ec1 | ||
| 
						 | 
					3d58416d9b | ||
| 
						 | 
					279090889f | ||
| 
						 | 
					8953e2cd42 | ||
| 
						 | 
					a48bcf25e4 | ||
| 
						 | 
					2df71c6571 | ||
| 
						 | 
					89a1ab3f6e | ||
| 
						 | 
					021b461b0a | ||
| 
						 | 
					5c9adcf597 | ||
| 
						 | 
					92bb274d03 | ||
| 
						 | 
					95a4acff36 | ||
| 
						 | 
					50ea0969e6 | ||
| 
						 | 
					6acbcb6704 | ||
| 
						 | 
					6c9b9ea30d | ||
| 
						 | 
					a4374602e1 | ||
| 
						 | 
					610eb0cfda | ||
| 
						 | 
					4f798263b1 | ||
| 
						 | 
					a0dd8e1c42 | ||
| 
						 | 
					754925c32c | ||
| 
						 | 
					4956166f76 | ||
| 
						 | 
					4f0ab6a376 | ||
| 
						 | 
					235c8d5ccb | ||
| 
						 | 
					ad96b50464 | ||
| 
						 | 
					a0cbf9ac6f | ||
| 
						 | 
					21ca50221f | ||
| 
						 | 
					26cfd2a38f | ||
| 
						 | 
					bb36ac0fb4 | ||
| 
						 | 
					fba1608edb | ||
| 
						 | 
					af18dba556 | ||
| 
						 | 
					8180fe5e99 | ||
| 
						 | 
					db78c70dc3 | ||
| 
						 | 
					2e8246fb2f | ||
| 
						 | 
					a53e3c987d | ||
| 
						 | 
					2f44ad0eff | ||
| 
						 | 
					14d6a5f11c | ||
| 
						 | 
					cf26fd970c | ||
| 
						 | 
					ed381658b5 | ||
| 4a9465f729 | |||
| 
						 | 
					4a3707efcf | ||
| 
						 | 
					ede91cde45 | ||
| 
						 | 
					88d2819394 | ||
| 
						 | 
					3c5c065b8d | ||
| 
						 | 
					4a932c8e23 | ||
| 
						 | 
					45d53fc9a9 | ||
| 
						 | 
					49cc7997f3 | ||
| 
						 | 
					2b23a65647 | ||
| 
						 | 
					772287225f | ||
| 
						 | 
					091cbb9e7f | ||
| 
						 | 
					0652215c18 | ||
| 
						 | 
					463144a062 | ||
| 
						 | 
					767c14044f | ||
| 
						 | 
					143fe1446e | ||
| 
						 | 
					00625eabd6 | ||
| 
						 | 
					63b02199f6 | ||
| 
						 | 
					050e6022cb | ||
| 
						 | 
					aff9304b1a | ||
| 
						 | 
					73dc43b329 | ||
| 
						 | 
					d43938236e | ||
| 
						 | 
					d0e1474f8f | ||
| 
						 | 
					7c44365f52 | ||
| 
						 | 
					144e7f8506 | ||
| 
						 | 
					8e80fa88be | ||
| 
						 | 
					762cc19c14 | ||
| 
						 | 
					dd3570fa37 | ||
| 
						 | 
					fd730741a4 | ||
| 
						 | 
					1986143dd0 | ||
| 
						 | 
					5b3779e4cb | ||
| 
						 | 
					98fa1f6ca8 | ||
| 
						 | 
					745fc7779e | ||
| 
						 | 
					c88b29a0ca | ||
| 
						 | 
					152bdd8061 | ||
| 
						 | 
					9f72f4b2f8 | ||
| 
						 | 
					e8acf8a127 | ||
| 
						 | 
					29c2ab0e4c | ||
| 
						 | 
					a2f1bef34a | ||
| 
						 | 
					2404ee3fce | ||
| 
						 | 
					e4ee33dcbe | ||
| 
						 | 
					2ad4c0bb91 | ||
| 
						 | 
					37b7ed8d83 | ||
| 
						 | 
					fd2ad33f61 | ||
| 
						 | 
					65f3318f73 | ||
| 
						 | 
					767f10323d | ||
| 
						 | 
					3232cce1b4 | ||
| 
						 | 
					d9b26e3fce | ||
| 
						 | 
					d89826db44 | ||
| 
						 | 
					8c9a983216 | ||
| 
						 | 
					a42b6198cb | ||
| 
						 | 
					fb555be86c | ||
| 
						 | 
					6c28684859 | ||
| 
						 | 
					c8a1377851 | ||
| 
						 | 
					9c0107e87c | ||
| 
						 | 
					e69e13d8da | ||
| 
						 | 
					b77d003238 | ||
| 
						 | 
					c1371bc35c | ||
| 
						 | 
					0bfe87793b | ||
| 
						 | 
					ce34345914 | ||
| 
						 | 
					1d3620845d | ||
| 
						 | 
					d766e3a7c3 | ||
| 
						 | 
					bfa176a7c5 | ||
| 
						 | 
					4abf59a10d | ||
| 
						 | 
					fb80e05d6a | ||
| 
						 | 
					11c55d422f | ||
| 
						 | 
					b8b62dc2c8 | ||
| 
						 | 
					99bf1106f3 | ||
| 
						 | 
					498ec3e9d8 | ||
| 
						 | 
					cf6143887e | ||
| 
						 | 
					39030a0a80 | ||
| 
						 | 
					950bbf1d7b | ||
| 
						 | 
					6ef8cde225 | ||
| 
						 | 
					9a6ab5c29c | ||
| 
						 | 
					4db037afd0 | ||
| 
						 | 
					c14e067449 | ||
| 
						 | 
					ddb4f76af7 | ||
| 
						 | 
					f64563c9ff | ||
| 
						 | 
					01bbb9be2c | ||
| 
						 | 
					4693b96130 | ||
| 
						 | 
					c5f45e03f3 | ||
| 
						 | 
					b7ecea25eb | ||
| 
						 | 
					649a1098c3 | ||
| 
						 | 
					e31815f576 | ||
| 
						 | 
					c6ea2195ed | ||
| 
						 | 
					d13249d87d | ||
| 
						 | 
					bfbcd58d1b | ||
| 
						 | 
					b6d9b6910f | ||
| 
						 | 
					f599131708 | ||
| 
						 | 
					fbf4040f63 | ||
| 
						 | 
					392cf1cf1a | ||
| 
						 | 
					8751d36b34 | ||
| 
						 | 
					9e795d795c | ||
| 
						 | 
					d848585b7c | ||
| 
						 | 
					bfa86de5ad | ||
| 
						 | 
					2d9b4587ac | ||
| 
						 | 
					4911e32ec2 | ||
| 
						 | 
					60fee8abe7 | ||
| 
						 | 
					3962b7c7aa | ||
| 
						 | 
					e0eb8cd4ad | ||
| 
						 | 
					e71bd3544f | ||
| 
						 | 
					6fe862a9b5 | ||
| 
						 | 
					92516e20ab | ||
| 
						 | 
					e51377eeb3 | ||
| 
						 | 
					f4603e4171 | ||
| 
						 | 
					5986d73367 | ||
| 
						 | 
					bc8c508e04 | ||
| 
						 | 
					2ac24779cd | ||
| 
						 | 
					4575f9ad3f | ||
| 
						 | 
					93b10de61d | ||
| 
						 | 
					eea7f25379 | ||
| 
						 | 
					62e245753b | ||
| 
						 | 
					6e3069068c | ||
| 
						 | 
					3b1bd41638 | ||
| 
						 | 
					f8cf8156bd | ||
| 
						 | 
					2ae15c720a | ||
| 
						 | 
					8dc009564a | ||
| 
						 | 
					4688d9c681 | ||
| 
						 | 
					5ac4492369 | ||
| 
						 | 
					14f585c657 | ||
| 
						 | 
					1cfd684e89 | ||
| 
						 | 
					580b492fd3 | ||
| 
						 | 
					f45ad91687 | ||
| 
						 | 
					47cd936131 | ||
| 
						 | 
					e0eefd9c30 | ||
| 
						 | 
					9c903907cd | ||
| 
						 | 
					9040d141c8 | ||
| 
						 | 
					0ec80473cc | ||
| 6df71bde1d | |||
| 
						 | 
					fe78927e73 | ||
| af9e7d8edd | |||
| b7c4a78158 | |||
| 1f83148aca | |||
| 
						 | 
					29fcd0e34a | ||
| 
						 | 
					d2484e84f0 | ||
| 17f62b6495 | |||
| 
						 | 
					dd5f8f5b2d | ||
| 
						 | 
					1e8cb98098 | ||
| 
						 | 
					e390dfa8a9 | ||
| 
						 | 
					9abc3a0d99 | ||
| 
						 | 
					1f09e48abf | ||
| 
						 | 
					0b5df45a02 | ||
| 
						 | 
					e320efd70e | ||
| 
						 | 
					caf0c1afcb | ||
| 
						 | 
					51541010e2 | ||
| 
						 | 
					89f14d99b8 | ||
| 
						 | 
					2525cc7783 | ||
| 
						 | 
					09b67b26b0 | ||
| 
						 | 
					24a277333d | ||
| 
						 | 
					efa73d8a45 | ||
| 
						 | 
					fa52c71e93 | ||
| 
						 | 
					e23756eba6 | ||
| 
						 | 
					e9dd17ae87 | ||
| 
						 | 
					d0e2778246 | ||
| 
						 | 
					9da3a7c3e6 | ||
| 
						 | 
					ed4cb943a8 | ||
| b125c9be01 | |||
| 7d93c60e36 | |||
| 
						 | 
					c3e6f04dd8 | ||
| 
						 | 
					69d53f9d4d | ||
| 
						 | 
					dbbda34117 | ||
| 
						 | 
					b0f2cf46eb | ||
| 
						 | 
					0256266107 | ||
| 
						 | 
					b0f5c78e55 | ||
| 
						 | 
					27327019be | ||
| 
						 | 
					1f8aefb7d8 | ||
| 
						 | 
					745e175da4 | ||
| 
						 | 
					924a688a9d | ||
| 
						 | 
					ad455e0e3a | ||
| 
						 | 
					51f3a320b3 | ||
| 
						 | 
					c4448fd3af | ||
| 
						 | 
					78bef28a21 | ||
| 
						 | 
					63236362ca | ||
| 
						 | 
					2795c924f7 | ||
| 
						 | 
					1ff4cb08d1 | ||
| 
						 | 
					4f27aefc4b | ||
| 
						 | 
					e311553eb1 | ||
| 
						 | 
					274ce57f5b | ||
| 
						 | 
					00fe19083b | ||
| 
						 | 
					7ae1399a9a | ||
| 
						 | 
					d6a2a416c1 | ||
| 
						 | 
					511e0c3f31 | ||
| 
						 | 
					19381d6438 | ||
| 
						 | 
					18d54cdb8f | ||
| 
						 | 
					b06193add2 | ||
| 
						 | 
					86c3e83eda | ||
| 
						 | 
					e784216584 | ||
| 
						 | 
					5405e91167 | ||
| 
						 | 
					250fefd0d8 | ||
| 
						 | 
					6ac855394b | ||
| 
						 | 
					4730d5b4d4 | ||
| 
						 | 
					5ab011844d | ||
| 
						 | 
					0e96b5c01a | ||
| 
						 | 
					50cecc40c4 | ||
| 
						 | 
					dc30412f2e | ||
| 
						 | 
					3e72510d53 | ||
| 
						 | 
					beeac40a10 | ||
| 
						 | 
					2f6fab2f7b | ||
| 
						 | 
					d77a403150 | ||
| 
						 | 
					8c3b92143e | ||
| 
						 | 
					e2f722e17b | ||
| 
						 | 
					956af8f5f5 | ||
| 
						 | 
					c7f65914bb | ||
| 
						 | 
					ff286583aa | ||
| 
						 | 
					fd64d423be | ||
| 
						 | 
					87b7cda0ce | ||
| 
						 | 
					eb1417853a | ||
| 
						 | 
					8c97fca96c | ||
| 
						 | 
					6c83e54d37 | ||
| 
						 | 
					d5c5b454f4 | ||
| 
						 | 
					5ad4d523ab | ||
| 
						 | 
					1ca983db36 | ||
| 2891dc8a68 | |||
| 
						 | 
					6f6cb7c489 | ||
| 
						 | 
					94b45387fb | ||
| 
						 | 
					094ae0a7f0 | ||
| 
						 | 
					51e94e73e5 | ||
| 
						 | 
					357d79e68b | ||
| 
						 | 
					ac2111a365 | ||
| 
						 | 
					637b2c977d | ||
| 
						 | 
					c5f4427f9f | ||
| 
						 | 
					045f3471ec | ||
| 
						 | 
					bde70dec9b | ||
| 
						 | 
					b775298bb0 | ||
| 
						 | 
					aa7ce56174 | ||
| 
						 | 
					00f25b48c0 | ||
| 
						 | 
					806f5d0f20 | ||
| 
						 | 
					8fb3e35142 | ||
| 
						 | 
					0b410b8580 | ||
| 
						 | 
					94cd62acfe | ||
| 
						 | 
					a687206a1b | ||
| 
						 | 
					635b1d4ed4 | ||
| 
						 | 
					88c8feb7d8 | ||
| 
						 | 
					febabe67e7 | ||
| 
						 | 
					0c825b65b0 | ||
| 
						 | 
					a6f2556050 | ||
| 
						 | 
					e44a6b65b7 | ||
| 
						 | 
					ebdbd97ce6 | ||
| 
						 | 
					4a90d3379c | ||
| 
						 | 
					7a49f6b957 | ||
| 
						 | 
					b1c3c056df | ||
| 
						 | 
					9f17532646 | ||
| 
						 | 
					f7dfbe91dd | ||
| 
						 | 
					b1293af229 | ||
| 
						 | 
					6696b0faac | ||
| 
						 | 
					a5741d5831 | ||
| 
						 | 
					bbe4b3eee7 | ||
| 
						 | 
					7f294374e8 | ||
| df556f7c7a | |||
| 
						 | 
					742bcd1acf | ||
| 
						 | 
					53811538c0 | ||
| 070f2d063e | |||
| e89b5aae84 | |||
| 3f9814e649 | |||
| cdc077c59e | |||
| 5c44b46bcb | |||
| d28662d70b | |||
| 650168f39c | |||
| 20913f056b | |||
| 2a9a784ec1 | |||
| 218714683b | |||
| 694957a483 | |||
| c494dba5ab | |||
| e838474835 | |||
| 071a49cd81 | |||
| 4f4e2949d6 | |||
| cc4cc49617 | |||
| 0a441cd3df | |||
| 3e9eae007b | |||
| d273fdc57f | |||
| 8eb88dcd82 | |||
| e3a7703bc5 | |||
| 08100eb57f | |||
| 6d104288b7 | |||
| 33c284cd4c | |||
| e345204851 | |||
| 4fab0cb153 | |||
| 7f1f85c00e | |||
| 2aa9054148 | |||
| c41eeb969a | |||
| 9bc77b6699 | |||
| 1999213f8b | |||
| 7d5bdadbef | |||
| 1dfff4635e | |||
| 85fc43368b | |||
| 29a75bced9 | |||
| 2eb74da9d2 | |||
| 2684703da5 | |||
| 02b0a2a595 | |||
| 90e9bcf378 | |||
| 4fd22dc131 | |||
| 562720089f | |||
| 3732187678 | |||
| f0eec45836 | |||
| 034a81cedb | |||
| 5ba33d3131 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
out/*
 | 
			
		||||
							
								
								
									
										660
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										660
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,660 @@
 | 
			
		||||
# GNU AFFERO GENERAL PUBLIC LICENSE
 | 
			
		||||
 | 
			
		||||
Version 3, 19 November 2007
 | 
			
		||||
 | 
			
		||||
Copyright (C) 2007 Free Software Foundation, Inc.
 | 
			
		||||
<https://fsf.org/>
 | 
			
		||||
 | 
			
		||||
Everyone is permitted to copy and distribute verbatim copies of this
 | 
			
		||||
license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
## Preamble
 | 
			
		||||
 | 
			
		||||
The GNU Affero General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works, specifically designed to ensure
 | 
			
		||||
cooperation with the community in the case of network server software.
 | 
			
		||||
 | 
			
		||||
The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works. By contrast,
 | 
			
		||||
our General Public Licenses are intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains
 | 
			
		||||
free software for all its users.
 | 
			
		||||
 | 
			
		||||
When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price. Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
Developers that use our General Public Licenses protect your rights
 | 
			
		||||
with two steps: (1) assert copyright on the software, and (2) offer
 | 
			
		||||
you this License which gives you legal permission to copy, distribute
 | 
			
		||||
and/or modify the software.
 | 
			
		||||
 | 
			
		||||
A secondary benefit of defending all users' freedom is that
 | 
			
		||||
improvements made in alternate versions of the program, if they
 | 
			
		||||
receive widespread use, become available for other developers to
 | 
			
		||||
incorporate. Many developers of free software are heartened and
 | 
			
		||||
encouraged by the resulting cooperation. However, in the case of
 | 
			
		||||
software used on network servers, this result may fail to come about.
 | 
			
		||||
The GNU General Public License permits making a modified version and
 | 
			
		||||
letting the public access it on a server without ever releasing its
 | 
			
		||||
source code to the public.
 | 
			
		||||
 | 
			
		||||
The GNU Affero General Public License is designed specifically to
 | 
			
		||||
ensure that, in such cases, the modified source code becomes available
 | 
			
		||||
to the community. It requires the operator of a network server to
 | 
			
		||||
provide the source code of the modified version running there to the
 | 
			
		||||
users of that server. Therefore, public use of a modified version, on
 | 
			
		||||
a publicly accessible server, gives the public access to the source
 | 
			
		||||
code of the modified version.
 | 
			
		||||
 | 
			
		||||
An older license, called the Affero General Public License and
 | 
			
		||||
published by Affero, was designed to accomplish similar goals. This is
 | 
			
		||||
a different license, not a version of the Affero GPL, but Affero has
 | 
			
		||||
released a new version of the Affero GPL which permits relicensing
 | 
			
		||||
under this license.
 | 
			
		||||
 | 
			
		||||
The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
## TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
### 0. Definitions.
 | 
			
		||||
 | 
			
		||||
"This License" refers to version 3 of the GNU Affero General Public
 | 
			
		||||
License.
 | 
			
		||||
 | 
			
		||||
"Copyright" also means copyright-like laws that apply to other kinds
 | 
			
		||||
of works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
"The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License. Each licensee is addressed as "you". "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of
 | 
			
		||||
an exact copy. The resulting work is called a "modified version" of
 | 
			
		||||
the earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy. Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies. Mere interaction with a user
 | 
			
		||||
through a computer network, with no transfer of a copy, is not
 | 
			
		||||
conveying.
 | 
			
		||||
 | 
			
		||||
An interactive user interface displays "Appropriate Legal Notices" to
 | 
			
		||||
the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License. If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
### 1. Source Code.
 | 
			
		||||
 | 
			
		||||
The "source code" for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it. "Object code" means any non-source form of
 | 
			
		||||
a work.
 | 
			
		||||
 | 
			
		||||
A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form. A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities. However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work. For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
The Corresponding Source need not include anything that users can
 | 
			
		||||
regenerate automatically from other parts of the Corresponding Source.
 | 
			
		||||
 | 
			
		||||
The Corresponding Source for a work in source code form is that same
 | 
			
		||||
work.
 | 
			
		||||
 | 
			
		||||
### 2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met. This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program. The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work. This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
You may make, run and propagate covered works that you do not convey,
 | 
			
		||||
without conditions so long as your license otherwise remains in force.
 | 
			
		||||
You may convey covered works to others for the sole purpose of having
 | 
			
		||||
them make modifications exclusively for you, or provide you with
 | 
			
		||||
facilities for running those works, provided that you comply with the
 | 
			
		||||
terms of this License in conveying all material for which you do not
 | 
			
		||||
control copyright. Those thus making or running the covered works for
 | 
			
		||||
you must do so exclusively on your behalf, under your direction and
 | 
			
		||||
control, on terms that prohibit them from making any copies of your
 | 
			
		||||
copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
Conveying under any other circumstances is permitted solely under the
 | 
			
		||||
conditions stated below. Sublicensing is not allowed; section 10 makes
 | 
			
		||||
it unnecessary.
 | 
			
		||||
 | 
			
		||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such
 | 
			
		||||
circumvention is effected by exercising rights under this License with
 | 
			
		||||
respect to the covered work, and you disclaim any intention to limit
 | 
			
		||||
operation or modification of the work as a means of enforcing, against
 | 
			
		||||
the work's users, your or third parties' legal rights to forbid
 | 
			
		||||
circumvention of technological measures.
 | 
			
		||||
 | 
			
		||||
### 4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
### 5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these
 | 
			
		||||
conditions:
 | 
			
		||||
 | 
			
		||||
-   a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
-   b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under
 | 
			
		||||
    section 7. This requirement modifies the requirement in section 4
 | 
			
		||||
    to "keep intact all notices".
 | 
			
		||||
-   c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy. This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged. This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
-   d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit. Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
### 6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
You may convey a covered work in object code form under the terms of
 | 
			
		||||
sections 4 and 5, provided that you also convey the machine-readable
 | 
			
		||||
Corresponding Source under the terms of this License, in one of these
 | 
			
		||||
ways:
 | 
			
		||||
 | 
			
		||||
-   a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
-   b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the Corresponding
 | 
			
		||||
    Source from a network server at no charge.
 | 
			
		||||
-   c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source. This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
-   d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge. You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code. If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source. Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
-   e) Convey the object code using peer-to-peer transmission,
 | 
			
		||||
    provided you inform other peers where the object code and
 | 
			
		||||
    Corresponding Source of the work are being offered to the general
 | 
			
		||||
    public at no charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal,
 | 
			
		||||
family, or household purposes, or (2) anything designed or sold for
 | 
			
		||||
incorporation into a dwelling. In determining whether a product is a
 | 
			
		||||
consumer product, doubtful cases shall be resolved in favor of
 | 
			
		||||
coverage. For a particular product received by a particular user,
 | 
			
		||||
"normally used" refers to a typical or common use of that class of
 | 
			
		||||
product, regardless of the status of the particular user or of the way
 | 
			
		||||
in which the particular user actually uses, or expects or is expected
 | 
			
		||||
to use, the product. A product is a consumer product regardless of
 | 
			
		||||
whether the product has substantial commercial, industrial or
 | 
			
		||||
non-consumer uses, unless such uses represent the only significant
 | 
			
		||||
mode of use of the product.
 | 
			
		||||
 | 
			
		||||
"Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to
 | 
			
		||||
install and execute modified versions of a covered work in that User
 | 
			
		||||
Product from a modified version of its Corresponding Source. The
 | 
			
		||||
information must suffice to ensure that the continued functioning of
 | 
			
		||||
the modified object code is in no case prevented or interfered with
 | 
			
		||||
solely because modification has been made.
 | 
			
		||||
 | 
			
		||||
If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information. But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or
 | 
			
		||||
updates for a work that has been modified or installed by the
 | 
			
		||||
recipient, or for the User Product in which it has been modified or
 | 
			
		||||
installed. Access to a network may be denied when the modification
 | 
			
		||||
itself materially and adversely affects the operation of the network
 | 
			
		||||
or violates the rules and protocols for communication across the
 | 
			
		||||
network.
 | 
			
		||||
 | 
			
		||||
Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
### 7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
"Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law. If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it. (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.) You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders
 | 
			
		||||
of that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
-   a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
-   b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
-   c) Prohibiting misrepresentation of the origin of that material,
 | 
			
		||||
    or requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
-   d) Limiting the use for publicity purposes of names of licensors
 | 
			
		||||
    or authors of the material; or
 | 
			
		||||
-   e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
-   f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions
 | 
			
		||||
    of it) with contractual assumptions of liability to the recipient,
 | 
			
		||||
    for any liability that these contractual assumptions directly
 | 
			
		||||
    impose on those licensors and authors.
 | 
			
		||||
 | 
			
		||||
All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10. If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term. If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions; the
 | 
			
		||||
above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
### 8. Termination.
 | 
			
		||||
 | 
			
		||||
You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License. Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
However, if you cease all violation of this License, then your license
 | 
			
		||||
from a particular copyright holder is reinstated (a) provisionally,
 | 
			
		||||
unless and until the copyright holder explicitly and finally
 | 
			
		||||
terminates your license, and (b) permanently, if the copyright holder
 | 
			
		||||
fails to notify you of the violation by some reasonable means prior to
 | 
			
		||||
60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License. If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
### 9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
You are not required to accept this License in order to receive or run
 | 
			
		||||
a copy of the Program. Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance. However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work. These actions infringe copyright if you do
 | 
			
		||||
not accept this License. Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
### 10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License. You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations. If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License. For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
### 11. Patents.
 | 
			
		||||
 | 
			
		||||
A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based. The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
A contributor's "essential patent claims" are all patent claims owned
 | 
			
		||||
or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version. For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement). To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients. "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
A patent license is "discriminatory" if it does not include within the
 | 
			
		||||
scope of its coverage, prohibits the exercise of, or is conditioned on
 | 
			
		||||
the non-exercise of one or more of the rights that are specifically
 | 
			
		||||
granted under this License. You may not convey a covered work if you
 | 
			
		||||
are a party to an arrangement with a third party that is in the
 | 
			
		||||
business of distributing software, under which you make payment to the
 | 
			
		||||
third party based on the extent of your activity of conveying the
 | 
			
		||||
work, and under which the third party grants, to any of the parties
 | 
			
		||||
who would receive the covered work from you, a discriminatory patent
 | 
			
		||||
license (a) in connection with copies of the covered work conveyed by
 | 
			
		||||
you (or copies made from those copies), or (b) primarily for and in
 | 
			
		||||
connection with specific products or compilations that contain the
 | 
			
		||||
covered work, unless you entered into that arrangement, or that patent
 | 
			
		||||
license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
### 12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License. If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under
 | 
			
		||||
this License and any other pertinent obligations, then as a
 | 
			
		||||
consequence you may not convey it at all. For example, if you agree to
 | 
			
		||||
terms that obligate you to collect a royalty for further conveying
 | 
			
		||||
from those to whom you convey the Program, the only way you could
 | 
			
		||||
satisfy both those terms and this License would be to refrain entirely
 | 
			
		||||
from conveying the Program.
 | 
			
		||||
 | 
			
		||||
### 13. Remote Network Interaction; Use with the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, if you modify the
 | 
			
		||||
Program, your modified version must prominently offer all users
 | 
			
		||||
interacting with it remotely through a computer network (if your
 | 
			
		||||
version supports such interaction) an opportunity to receive the
 | 
			
		||||
Corresponding Source of your version by providing access to the
 | 
			
		||||
Corresponding Source from a network server at no charge, through some
 | 
			
		||||
standard or customary means of facilitating copying of software. This
 | 
			
		||||
Corresponding Source shall include the Corresponding Source for any
 | 
			
		||||
work covered by version 3 of the GNU General Public License that is
 | 
			
		||||
incorporated pursuant to the following paragraph.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work. The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the work with which it is combined will remain governed by version
 | 
			
		||||
3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
### 14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the GNU Affero General Public License from time to time. Such new
 | 
			
		||||
versions will be similar in spirit to the present version, but may
 | 
			
		||||
differ in detail to address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
Each version is given a distinguishing version number. If the Program
 | 
			
		||||
specifies that a certain numbered version of the GNU Affero General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation. If the Program does not specify a version number of the
 | 
			
		||||
GNU Affero General Public License, you may choose any version ever
 | 
			
		||||
published by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
If the Program specifies that a proxy can decide which future versions
 | 
			
		||||
of the GNU Affero General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
Later license versions may give you additional or different
 | 
			
		||||
permissions. However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
### 15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
 | 
			
		||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
 | 
			
		||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
 | 
			
		||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
 | 
			
		||||
CORRECTION.
 | 
			
		||||
 | 
			
		||||
### 16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
 | 
			
		||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
 | 
			
		||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
 | 
			
		||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
 | 
			
		||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
 | 
			
		||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
 | 
			
		||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
### 17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
## How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these
 | 
			
		||||
terms.
 | 
			
		||||
 | 
			
		||||
To do so, attach the following notices to the program. It is safest to
 | 
			
		||||
attach them to the start of each source file to most effectively state
 | 
			
		||||
the exclusion of warranty; and each file should have at least the
 | 
			
		||||
"copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
        <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
        Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
        This program is free software: you can redistribute it and/or modify
 | 
			
		||||
        it under the terms of the GNU Affero General Public License as
 | 
			
		||||
        published by the Free Software Foundation, either version 3 of the
 | 
			
		||||
        License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
        This program 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 Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
        You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper
 | 
			
		||||
mail.
 | 
			
		||||
 | 
			
		||||
If your software can interact with users remotely through a computer
 | 
			
		||||
network, you should also make sure that it provides a way for users to
 | 
			
		||||
get its source. For example, if your program is a web application, its
 | 
			
		||||
interface could display a "Source" link that leads users to an archive
 | 
			
		||||
of the code. There are many ways you could offer source, and different
 | 
			
		||||
solutions will be better for different programs; see section 13 for
 | 
			
		||||
the specific requirements.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary. For more information on this, and how to apply and follow
 | 
			
		||||
the GNU AGPL, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
							
								
								
									
										101
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								README.md
									
									
									
									
									
								
							@@ -1,2 +1,103 @@
 | 
			
		||||
# oc-lib
 | 
			
		||||
 | 
			
		||||
oc-lib allows read/write/search operations into the main OpenCloud databases.
 | 
			
		||||
 | 
			
		||||
It also provides common initialization and configuration utilities for all OpenCloud components
 | 
			
		||||
 | 
			
		||||
## Usage example in a beego API
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
const appname = "oc-mycomponent"
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
    // Init the oc-lib
 | 
			
		||||
    oclib.Init(appname)
 | 
			
		||||
 | 
			
		||||
    // Load the right config file
 | 
			
		||||
 | 
			
		||||
    /*  The configuration loader will load the configuration from the following sources:
 | 
			
		||||
    *  - the environment variables with the prefix OCAPPNAME_ - ex: OCMYCOMPONENT_MONGOURL
 | 
			
		||||
    *  - the file /etc/oc/appname.json - ex: /etc/oc/mycomponent.json
 | 
			
		||||
    *  - the file ./appname.json - ex: ./mycomponent.json
 | 
			
		||||
    *  The configuration loader will merge the configuration from the different sources
 | 
			
		||||
    *  The configuration loader will give priority to the environment variables
 | 
			
		||||
    *  The configuration loader will give priority to the local file over the default file
 | 
			
		||||
    */
 | 
			
		||||
    o := oclib.GetConfLoader()
 | 
			
		||||
 | 
			
		||||
    // init the local config object
 | 
			
		||||
    models.GetConfig().Port = o.GetIntDefault("port", 8080)
 | 
			
		||||
    models.GetConfig().LokiUrl = o.GetStringDefault("lokiurl", "")
 | 
			
		||||
    models.GetConfig().LogLevel = o.GetStringDefault("loglevel", "info")    
 | 
			
		||||
    models.GetConfig().MongoUrl = o.GetStringDefault("mongourl", "mongodb://127.0.0.1:27017")
 | 
			
		||||
    models.GetConfig().MongoDatabase = o.GetStringDefault("mongodatabase", "myDb")
 | 
			
		||||
    models.GetConfig().NatsUrl = o.GetStringDefault("natsurl", "nats://localhost:4222")
 | 
			
		||||
 | 
			
		||||
    models.GetConfig().mycomponentparam1 = o.GetStringDefault("mycomponentparam1", "mycomponentdefault1")    
 | 
			
		||||
    models.GetConfig().mycomponentparam2 = o.GetStringDefault("mycomponentparam2", "mycomponentdefault2")
 | 
			
		||||
 | 
			
		||||
    // feed the library with the loaded config, 
 | 
			
		||||
    // this will also initialize a logger available via oclib.GetLogger()
 | 
			
		||||
	oclib.SetConfig(
 | 
			
		||||
        models.GetConfig().MongoUrl
 | 
			
		||||
        models.GetConfig().MongoDatabase 
 | 
			
		||||
        models.GetConfig().NatsUrl
 | 
			
		||||
        models.GetConfig().LokiUrl
 | 
			
		||||
        models.GetConfig().LogLevel
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
    // Beego init
 | 
			
		||||
    beego.BConfig.AppName = appname
 | 
			
		||||
    beego.BConfig.Listen.HTTPPort = models.GetConfig().Port
 | 
			
		||||
    beego.BConfig.WebConfig.DirectoryIndex = true
 | 
			
		||||
    beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 | 
			
		||||
 | 
			
		||||
    beego.Run()
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## SPECIAL FLOWS IN OC-LIB RESUME :
 | 
			
		||||
 | 
			
		||||
### WORKFLOW AS ITS OWN WORKSPACE
 | 
			
		||||
 | 
			
		||||
A workflow on post, put, delete, manage a workspace with resources.
 | 
			
		||||
This workspace is deeply related to workflow by sharing its naming such as : "<workflow_name>_worspace"
 | 
			
		||||
 | 
			
		||||
### WORKFLOW GENERATE ITS OWN EXECUTION
 | 
			
		||||
 | 
			
		||||
A workflow on post, put, delete, with a schedule && schedule_active to "true", must manage execution by adding, deleting execution 
 | 
			
		||||
depending on change (update -> involved delete into add)
 | 
			
		||||
 | 
			
		||||
If schedule_active is set to "false" execution will be deleted.
 | 
			
		||||
 | 
			
		||||
### WORKFLOW GENERATE ITS OWN BOOKING ON PEERS
 | 
			
		||||
 | 
			
		||||
A workflow on post, put, delete, with a schedule && schedule_active to "true", must manage booking by adding, deleting execution 
 | 
			
		||||
with http requests on peers
 | 
			
		||||
depending on change (update -> involved delete into add)
 | 
			
		||||
 | 
			
		||||
If schedule_active is set to "false" booking will be deleted.
 | 
			
		||||
 | 
			
		||||
### SHARED WORKSPACE : WORSPACE & WORKFLOW
 | 
			
		||||
 | 
			
		||||
You can create a share workspace with workspace & workflow.
 | 
			
		||||
When a share workspace is post, put, delete it update workspace or workflow <shared> field.
 | 
			
		||||
Workspace can be shared on one share workspace 
 | 
			
		||||
Workflow can be shared in multiple workspace 
 | 
			
		||||
 | 
			
		||||
### SHARED WORKSPACE SHARE TO PEER
 | 
			
		||||
 | 
			
		||||
When writing a shared workspace, it set up to date on peers involved in shared workspace
 | 
			
		||||
It create or delete shared workspace in remote peers by http requests on oc-shared  (update -> involved delete into add)
 | 
			
		||||
 | 
			
		||||
It create or delete workspace involved in shared workspace by http requests on oc-workspace  (update -> involved delete into add)
 | 
			
		||||
It create or delete workflow involved in shared workspace by http requests on oc-workflow (update -> involved delete into add)
 | 
			
		||||
 | 
			
		||||
### WORKFLOW WRITE BUT SHARED
 | 
			
		||||
 | 
			
		||||
On delete & update & post, workflow will send to peer in <shared> field by http request on oc-workflow
 | 
			
		||||
 | 
			
		||||
### WORKSPACE WRITE BUT SHARED
 | 
			
		||||
 | 
			
		||||
On delete & update & post, workspace will send to peer in <shared> field by http request on oc-workspace
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
							
								
								
									
										19
									
								
								config/app.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								config/app.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
var appname string
 | 
			
		||||
 | 
			
		||||
// logs.CreateLogger
 | 
			
		||||
// Create a new logger
 | 
			
		||||
// Parameters:
 | 
			
		||||
// - appname: string : the name of the application using oclib
 | 
			
		||||
// - url: string : the url of a loki logger, console log only if ""
 | 
			
		||||
// Returns:
 | 
			
		||||
// - zerolog.Logger : the logger that will log for the library and the app
 | 
			
		||||
 | 
			
		||||
func SetAppName(name string) {
 | 
			
		||||
	appname = name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAppName() string {
 | 
			
		||||
	return appname
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								config/conf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								config/conf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "sync"
 | 
			
		||||
 | 
			
		||||
// ===================================================
 | 
			
		||||
//	This class has to be updated everytime
 | 
			
		||||
//	a new configuration variable is defined
 | 
			
		||||
//	in a componant that imports oc-lib
 | 
			
		||||
// ===================================================
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	NATSUrl       string
 | 
			
		||||
	MongoUrl      string
 | 
			
		||||
	MongoDatabase string
 | 
			
		||||
	Host          string
 | 
			
		||||
	Port          string
 | 
			
		||||
	LokiUrl       string
 | 
			
		||||
	LogLevel      string
 | 
			
		||||
	Whitelist     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) GetUrl() string {
 | 
			
		||||
	return c.MongoUrl
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) GetDatabase() string {
 | 
			
		||||
	return c.MongoDatabase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var instance *Config
 | 
			
		||||
var once sync.Once
 | 
			
		||||
 | 
			
		||||
func GetConfig() *Config {
 | 
			
		||||
	once.Do(func() {
 | 
			
		||||
		instance = &Config{}
 | 
			
		||||
	})
 | 
			
		||||
	return instance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *Config {
 | 
			
		||||
	GetConfig().MongoUrl = mongoUrl
 | 
			
		||||
	GetConfig().MongoDatabase = database
 | 
			
		||||
	GetConfig().NATSUrl = natsUrl
 | 
			
		||||
	GetConfig().LokiUrl = lokiUrl
 | 
			
		||||
	GetConfig().LogLevel = logLevel
 | 
			
		||||
	GetConfig().Whitelist = true
 | 
			
		||||
	return GetConfig()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								config/conf_loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								config/conf_loader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/goraz/onion"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* GetConfLoader
 | 
			
		||||
* Get the configuration loader for the application
 | 
			
		||||
* Parameters:
 | 
			
		||||
* - AppName: string : the name of the application
 | 
			
		||||
* Returns:
 | 
			
		||||
* - *onion.Onion : the configuration loader
 | 
			
		||||
*  The configuration loader will load the configuration from the following sources:
 | 
			
		||||
*  - the environment variables with the prefix APPNAME_
 | 
			
		||||
*  - the file /etc/oc/appname.json
 | 
			
		||||
*  - the file ./appname.json
 | 
			
		||||
*  The configuration loader will merge the configuration from the different sources
 | 
			
		||||
*  The configuration loader will give priority to the environment variables
 | 
			
		||||
*  The configuration loader will give priority to the local file over the default file
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func GetConfLoader() *onion.Onion {
 | 
			
		||||
	logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
 | 
			
		||||
	AppName := GetAppName()
 | 
			
		||||
	EnvPrefix := "OC_"
 | 
			
		||||
	defaultConfigFile := "/etc/oc/" + AppName[3:] + ".json"
 | 
			
		||||
	localConfigFile := "./" + AppName[3:] + ".json"
 | 
			
		||||
	var configFile string
 | 
			
		||||
	var o *onion.Onion
 | 
			
		||||
	l3 := GetEnvVarLayer(EnvPrefix)
 | 
			
		||||
	l2, err := onion.NewFileLayer(localConfigFile, nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		logger.Info().Msg("Local config file found " + localConfigFile + ", overriding default file")
 | 
			
		||||
		configFile = localConfigFile
 | 
			
		||||
	}
 | 
			
		||||
	l1, err := onion.NewFileLayer(defaultConfigFile, nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		logger.Info().Msg("Config file found : " + defaultConfigFile)
 | 
			
		||||
		configFile = defaultConfigFile
 | 
			
		||||
	}
 | 
			
		||||
	if configFile == "" {
 | 
			
		||||
		logger.Info().Msg("No config file found, using env")
 | 
			
		||||
		o = onion.New(l3)
 | 
			
		||||
	} else if l1 != nil && l2 != nil {
 | 
			
		||||
		o = onion.New(l1, l2, l3)
 | 
			
		||||
	} else if l1 == nil {
 | 
			
		||||
		o = onion.New(l2, l3)
 | 
			
		||||
	} else if l2 == nil {
 | 
			
		||||
		o = onion.New(l1, l3)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetEnvVarLayer(prefix string) onion.Layer {
 | 
			
		||||
	envVars := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	for _, e := range os.Environ() {
 | 
			
		||||
		pair := strings.SplitN(e, "=", 2)
 | 
			
		||||
		key := pair[0]
 | 
			
		||||
		if strings.HasPrefix(key, prefix) {
 | 
			
		||||
			envVars[strings.TrimPrefix(key, prefix)] = pair[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return onion.NewMapLayer(envVars)
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
							
								
								
									
										190
									
								
								dbs/dbs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								dbs/dbs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
package dbs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Operator int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	LIKE Operator = iota
 | 
			
		||||
	EXISTS
 | 
			
		||||
	IN
 | 
			
		||||
	GTE
 | 
			
		||||
	LTE
 | 
			
		||||
	LT
 | 
			
		||||
	GT
 | 
			
		||||
	EQUAL
 | 
			
		||||
	NOT
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var str = [...]string{
 | 
			
		||||
	"like",
 | 
			
		||||
	"exists",
 | 
			
		||||
	"in",
 | 
			
		||||
	"gte",
 | 
			
		||||
	"lte",
 | 
			
		||||
	"lt",
 | 
			
		||||
	"gt",
 | 
			
		||||
	"equal",
 | 
			
		||||
	"not",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Operator) String() string {
 | 
			
		||||
	return str[m]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Operator) ToMongoEOperator(k string, value interface{}) bson.E {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			fmt.Println("Recovered. Error:\n", r)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	defaultValue := bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	switch m {
 | 
			
		||||
	case LIKE:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case EXISTS:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case IN:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case GTE:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case GT:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case LTE:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case LT:
 | 
			
		||||
		return bson.E{Key: k, Value: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case EQUAL:
 | 
			
		||||
		return bson.E{Key: k, Value: value}
 | 
			
		||||
	case NOT:
 | 
			
		||||
		v := value.(Filters)
 | 
			
		||||
		orList := bson.A{}
 | 
			
		||||
		andList := bson.A{}
 | 
			
		||||
		f := bson.D{}
 | 
			
		||||
		for k, filter := range v.Or {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k, filter := range v.And {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(orList) > 0 && len(andList) == 0 {
 | 
			
		||||
			f = bson.D{{"$or", orList}}
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(orList) > 0 {
 | 
			
		||||
				andList = append(andList, bson.M{"$or": orList})
 | 
			
		||||
			}
 | 
			
		||||
			f = bson.D{{"$and", andList}}
 | 
			
		||||
		}
 | 
			
		||||
		return bson.E{Key: "$not", Value: f}
 | 
			
		||||
	default:
 | 
			
		||||
		return defaultValue
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Operator) ToMongoOperator(k string, value interface{}) bson.M {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			fmt.Println("Recovered. Error:\n", r)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	defaultValue := bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	switch m {
 | 
			
		||||
	case LIKE:
 | 
			
		||||
		return bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case EXISTS:
 | 
			
		||||
		return bson.M{k: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case IN:
 | 
			
		||||
		return bson.M{k: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case GTE:
 | 
			
		||||
		return bson.M{k: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case GT:
 | 
			
		||||
		return bson.M{k: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case LTE:
 | 
			
		||||
		return bson.M{k: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case LT:
 | 
			
		||||
		return bson.M{k: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}}
 | 
			
		||||
	case EQUAL:
 | 
			
		||||
		return bson.M{k: value}
 | 
			
		||||
	case NOT:
 | 
			
		||||
		v := value.(Filters)
 | 
			
		||||
		orList := bson.A{}
 | 
			
		||||
		andList := bson.A{}
 | 
			
		||||
		f := bson.D{}
 | 
			
		||||
		for k, filter := range v.Or {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k, filter := range v.And {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(orList) > 0 && len(andList) == 0 {
 | 
			
		||||
			f = bson.D{{"$or", orList}}
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(orList) > 0 {
 | 
			
		||||
				andList = append(andList, bson.M{"$or": orList})
 | 
			
		||||
			}
 | 
			
		||||
			f = bson.D{{"$and", andList}}
 | 
			
		||||
		}
 | 
			
		||||
		return bson.M{"$not": f}
 | 
			
		||||
	default:
 | 
			
		||||
		return defaultValue
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func StringToOperator(s string) Operator {
 | 
			
		||||
	for i, v := range str {
 | 
			
		||||
		if v == s {
 | 
			
		||||
			return Operator(i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return LIKE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToValueOperator(operator Operator, value interface{}) interface{} {
 | 
			
		||||
	if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
 | 
			
		||||
		value = ""
 | 
			
		||||
	}
 | 
			
		||||
	if operator == LIKE {
 | 
			
		||||
		return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*"
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Filters struct {
 | 
			
		||||
	And map[string][]Filter `json:"and"`
 | 
			
		||||
	Or  map[string][]Filter `json:"or"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Filter struct {
 | 
			
		||||
	Operator string      `json:"operator,omitempty"`
 | 
			
		||||
	Value    interface{} `json:"value,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Input = map[string]interface{}
 | 
			
		||||
 | 
			
		||||
func InputToBson(i Input, isUpdate bool) bson.D {
 | 
			
		||||
	input := bson.D{}
 | 
			
		||||
	for k, v := range i {
 | 
			
		||||
		if k == "id" {
 | 
			
		||||
			input = append(input, bson.E{Key: "_id", Value: v})
 | 
			
		||||
		} else {
 | 
			
		||||
			input = append(input, bson.E{Key: k, Value: v})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isUpdate {
 | 
			
		||||
		return bson.D{{Key: "$set", Value: input}}
 | 
			
		||||
	}
 | 
			
		||||
	return input
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										365
									
								
								dbs/mongo/mongo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								dbs/mongo/mongo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,365 @@
 | 
			
		||||
package mongo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/mongo"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/mongo/options"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	mngoClient          *mongo.Client
 | 
			
		||||
	mngoDB              *mongo.Database
 | 
			
		||||
	MngoCtx             context.Context
 | 
			
		||||
	cancel              context.CancelFunc
 | 
			
		||||
	isConnected         bool
 | 
			
		||||
	existingCollections []string
 | 
			
		||||
	mngoCollections     []string
 | 
			
		||||
	mngoConfig          MongoConf
 | 
			
		||||
	ResourceMap         map[string]interface{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var MONGOService = MongoDB{}
 | 
			
		||||
 | 
			
		||||
type MongoConf interface {
 | 
			
		||||
	GetUrl() string
 | 
			
		||||
	GetDatabase() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MongoDB struct {
 | 
			
		||||
	Logger zerolog.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) Init(collections []string, config MongoConf) {
 | 
			
		||||
	// var baseConfig string
 | 
			
		||||
	isConnected = false
 | 
			
		||||
	m.Logger = logs.GetLogger()
 | 
			
		||||
	ResourceMap = make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	m.Logger.Info().Msg("Connecting to" + config.GetUrl())
 | 
			
		||||
	mngoCollections = collections
 | 
			
		||||
	mngoConfig = config
 | 
			
		||||
	if err := m.createClient(config.GetUrl(), false); err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) TestDB(config MongoConf) error {
 | 
			
		||||
	err := m.createClient(config.GetUrl(), true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MongoDB) TestCollections(config MongoConf, neededCols []string) error {
 | 
			
		||||
	mngoDB = mngoClient.Database(config.GetDatabase())
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	existingCollections, err := mngoDB.ListCollectionNames(MngoCtx, bson.D{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Error contacting MongoDB\n" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	for _, col := range neededCols {
 | 
			
		||||
		if slices.Contains(existingCollections, col) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		return errors.New("Collection " + col + " not found")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) createClient(MongoURL string, test bool) error {
 | 
			
		||||
	if mngoClient != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	// Allows us to use marshal and unmarshall with results of FindOne() and others
 | 
			
		||||
	bsonOpts := &options.BSONOptions{
 | 
			
		||||
		UseJSONStructTags: true,
 | 
			
		||||
		NilSliceAsEmpty:   true,
 | 
			
		||||
	}
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
	clientOptions := options.Client().ApplyURI(MongoURL).SetBSONOptions(bsonOpts)
 | 
			
		||||
	// Ping the primary
 | 
			
		||||
	if mngoClient, err = mongo.Connect(MngoCtx, clientOptions); err != nil || mngoClient == nil {
 | 
			
		||||
		mngoClient = nil
 | 
			
		||||
		isConnected = false
 | 
			
		||||
		return errors.New("Mongodb connect " + MongoURL + ":" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
	if err = mngoClient.Ping(MngoCtx, nil); err != nil {
 | 
			
		||||
		mngoClient = nil
 | 
			
		||||
		isConnected = false
 | 
			
		||||
		return errors.New("Mongodb ping " + MongoURL + ":" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	if !isConnected && mngoClient != nil && !test {
 | 
			
		||||
		m.Logger.Info().Msg("Connecting mongo client to db " + mngoConfig.GetDatabase())
 | 
			
		||||
		m.prepareDB(mngoCollections, mngoConfig)
 | 
			
		||||
		m.Logger.Info().Msg("Database is READY")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) prepareDB(list_collection []string, config MongoConf) {
 | 
			
		||||
	var err error
 | 
			
		||||
	mngoDB = mngoClient.Database(config.GetDatabase())
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	existingCollections, err = mngoDB.ListCollectionNames(MngoCtx, bson.D{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Fatal().Msg("Error contacting MongoDB\n" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	collectionMap := make(map[string]bool)
 | 
			
		||||
	for _, name := range existingCollections {
 | 
			
		||||
		collectionMap[name] = true
 | 
			
		||||
	}
 | 
			
		||||
	// Only do the collection definition process if it doesn't already exists
 | 
			
		||||
	// we add the collection to the collection map from mongo/mongo_utils to provide faster access to the collection
 | 
			
		||||
	for _, collection_name := range list_collection {
 | 
			
		||||
		new_collection := mngoDB.Collection(collection_name)
 | 
			
		||||
		if _, exists := collectionMap[collection_name]; !exists {
 | 
			
		||||
			m.createCollection(collection_name, new_collection)
 | 
			
		||||
		} else {
 | 
			
		||||
			CollectionMap[collection_name] = new_collection
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	isConnected = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Creates the collection with index specified in mongo/mongo_collections
 | 
			
		||||
// or use the basic collection creation function
 | 
			
		||||
func (m *MongoDB) createCollection(collection_name string, new_collection *mongo.Collection) {
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	var err error
 | 
			
		||||
	CollectionMap[collection_name] = new_collection
 | 
			
		||||
	_, exists := IndexesMap[collection_name]
 | 
			
		||||
	if exists {
 | 
			
		||||
		if _, err = new_collection.Indexes().CreateMany(MngoCtx, IndexesMap[collection_name]); err != nil {
 | 
			
		||||
			var cmdErr mongo.CommandError
 | 
			
		||||
			if errors.As(err, &cmdErr) && cmdErr.Code != 85 {
 | 
			
		||||
				m.Logger.Fatal().Msg("Error creating indexes for " + collection_name + " collection : \n" + err.Error())
 | 
			
		||||
				panic(err)
 | 
			
		||||
			} else if !errors.As(err, &cmdErr) {
 | 
			
		||||
				m.Logger.Fatal().Msg("Unexpected error: " + err.Error())
 | 
			
		||||
				panic(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		mngoDB.CreateCollection(MngoCtx, collection_name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) DeleteOne(id string, collection_name string) (int64, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return 0, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	result, err := targetDBCollection.DeleteOne(MngoCtx, filter, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return result.DeletedCount, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) DeleteMultiple(f map[string]interface{}, collection_name string) (int64, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return 0, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	filter := bson.D{}
 | 
			
		||||
	for k, v := range f {
 | 
			
		||||
		filter = append(filter, bson.E{Key: k, Value: v})
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	result, err := targetDBCollection.DeleteMany(MngoCtx, filter, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return result.DeletedCount, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) UpdateMultiple(set interface{}, filter map[string]interface{}, collection_name string) (int64, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return 0, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	var doc map[string]interface{}
 | 
			
		||||
	b, _ := bson.Marshal(set)
 | 
			
		||||
	bson.Unmarshal(b, &doc)
 | 
			
		||||
	f := bson.D{}
 | 
			
		||||
	for k, v := range filter {
 | 
			
		||||
		f = append(f, bson.E{Key: k, Value: v})
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
	res, err := targetDBCollection.UpdateMany(MngoCtx, f, dbs.InputToBson(doc, true))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res.UpsertedCount, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) UpdateOne(set interface{}, id string, collection_name string) (string, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return "", 503, err
 | 
			
		||||
	}
 | 
			
		||||
	var doc map[string]interface{}
 | 
			
		||||
	b, _ := bson.Marshal(set)
 | 
			
		||||
	bson.Unmarshal(b, &doc)
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
	_, err := targetDBCollection.UpdateOne(MngoCtx, filter, dbs.InputToBson(doc, true))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		return "", 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return id, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) StoreOne(obj interface{}, id string, collection_name string) (string, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return "", 503, err
 | 
			
		||||
	}
 | 
			
		||||
	var doc map[string]interface{}
 | 
			
		||||
	b, _ := bson.Marshal(obj)
 | 
			
		||||
	bson.Unmarshal(b, &doc)
 | 
			
		||||
	doc["_id"] = id
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	_, err := targetDBCollection.InsertOne(MngoCtx, doc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return "", 409, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return id, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) LoadOne(id string, collection_name string) (*mongo.SingleResult, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return nil, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res := targetDBCollection.FindOne(MngoCtx, filter)
 | 
			
		||||
	if res.Err() != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find resource " + id + ". Error : " + res.Err().Error())
 | 
			
		||||
		err := res.Err()
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.Cursor, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return nil, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	opts := options.Find()
 | 
			
		||||
	opts.SetLimit(1000)
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	orList := bson.A{}
 | 
			
		||||
	andList := bson.A{}
 | 
			
		||||
	f := bson.D{}
 | 
			
		||||
	if filters != nil {
 | 
			
		||||
		for k, filter := range filters.Or {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				orList = append(orList, dbs.StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k, filter := range filters.And {
 | 
			
		||||
			for _, ff := range filter {
 | 
			
		||||
				andList = append(andList, dbs.StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(orList) > 0 && len(andList) == 0 {
 | 
			
		||||
			f = bson.D{{"$or", orList}}
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(orList) > 0 {
 | 
			
		||||
				andList = append(andList, bson.M{"$or": orList})
 | 
			
		||||
			}
 | 
			
		||||
			f = bson.D{{"$and", andList}}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	// defer cancel()
 | 
			
		||||
	if cursor, err := targetDBCollection.Find(
 | 
			
		||||
		MngoCtx,
 | 
			
		||||
		f,
 | 
			
		||||
		opts,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	} else {
 | 
			
		||||
		return cursor, 200, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) LoadFilter(filter map[string]interface{}, collection_name string) (*mongo.Cursor, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return nil, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	f := bson.D{}
 | 
			
		||||
	for k, v := range filter {
 | 
			
		||||
		f = append(f, bson.E{Key: k, Value: v})
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res, err := targetDBCollection.Find(MngoCtx, f)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MongoDB) LoadAll(collection_name string) (*mongo.Cursor, int, error) {
 | 
			
		||||
	if err := m.createClient(mngoConfig.GetUrl(), false); err != nil {
 | 
			
		||||
		return nil, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res, err := targetDBCollection.Find(MngoCtx, bson.D{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								dbs/mongo/mongo_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								dbs/mongo/mongo_utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
package mongo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/mongo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Will store the created collection object for a faster access
 | 
			
		||||
var CollectionMap map[string]*mongo.Collection
 | 
			
		||||
var IndexesMap map[string][]mongo.IndexModel
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	CollectionMap = make(map[string]*mongo.Collection)
 | 
			
		||||
	IndexesMap = make(map[string][]mongo.IndexModel)
 | 
			
		||||
 | 
			
		||||
	IndexesMap["data"] = append(IndexesMap["data"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"}},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	IndexesMap["compute"] = append(IndexesMap["compute"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"},
 | 
			
		||||
		{Key: "owner", Value: "text"}},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	IndexesMap["storage"] = append(IndexesMap["storage"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"}},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	IndexesMap["processing"] = append(IndexesMap["processing"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"},
 | 
			
		||||
		{Key: "owner", Value: "text"},
 | 
			
		||||
	},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	IndexesMap["workflow"] = append(IndexesMap["workflow"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"},
 | 
			
		||||
		{Key: "owner", Value: "text"},
 | 
			
		||||
	},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetObjIDFromString(id string) interface{} {
 | 
			
		||||
	objectID, err := primitive.ObjectIDFromHex(id)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return objectID
 | 
			
		||||
	}
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								doc/model.puml
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								doc/model.puml
									
									
									
									
									
								
							@@ -1,22 +1,22 @@
 | 
			
		||||
@startuml oclib
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
abstract Ressource {
 | 
			
		||||
    +id: int
 | 
			
		||||
abstract Resource{
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +icon: string
 | 
			
		||||
    +description: string
 | 
			
		||||
    +graphic: GraphicElement
 | 
			
		||||
    +element: Data/Processing/Storage/Workflow/Datacenter
 | 
			
		||||
    +element: DataResource/ProcessingResource/StorageResource/Workflow/ComputeResource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Data {
 | 
			
		||||
    +id: int
 | 
			
		||||
class DataResource {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Processing {
 | 
			
		||||
    +id: int
 | 
			
		||||
class ProcessingResource {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +container: string
 | 
			
		||||
    +command: int
 | 
			
		||||
@@ -24,110 +24,154 @@ class Processing {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Storage {
 | 
			
		||||
    +id: int
 | 
			
		||||
class StorageResource {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +url: string
 | 
			
		||||
    +capacity: int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Datacenter {
 | 
			
		||||
    +id: int
 | 
			
		||||
class ComputeResource {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Workflow {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ResourceSet {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +ressources: Ressource[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WorkflowSchedule {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +start: date
 | 
			
		||||
    +end: date
 | 
			
		||||
    +cron : string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Graph {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +ressources: map[GraphicElement.ID]Ressource 
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +ressources: map[GraphicElement.UUID]Resource
 | 
			
		||||
    +links: Link[] 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Link {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +source: GraphicElement.ID
 | 
			
		||||
    +target: GraphicElement.ID
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +source: GraphicElement.UUID
 | 
			
		||||
    +target: GraphicElement.UUID
 | 
			
		||||
    +graphic: GraphicLink
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class GraphicLink {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +startXY: coord
 | 
			
		||||
    +endXY: coord
 | 
			
		||||
    +style: string
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
class GraphicElement {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +style: string
 | 
			
		||||
    +xy: coord
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Calendar {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
    +owner: string
 | 
			
		||||
}  
 | 
			
		||||
 | 
			
		||||
class UserWorkflows {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +user: string
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
class DatacenterWorkflows {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +datacenter: Datacenter
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +compute: ComputeResource
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
class Graph {
 | 
			
		||||
    +id: int
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +graph: Graph
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Workspace {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +resources: ResourceSet[]
 | 
			
		||||
    +peers: Peer[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Peer {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +url: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SharedWorkspace {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +version: string
 | 
			
		||||
    +description: string
 | 
			
		||||
    +attributes: map[string]string
 | 
			
		||||
    +workspaces: Workspace[]
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
    +peers: Peer[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class RuleSet {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +description: string
 | 
			
		||||
    +rules: Rule[]
 | 
			
		||||
}
 | 
			
		||||
class Rule {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
    +description: string
 | 
			
		||||
    +condition: string
 | 
			
		||||
    +action: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SharedWorkspace "1" o-- "0..*" Workspace
 | 
			
		||||
SharedWorkspace "1" o-- "0..*" Peer
 | 
			
		||||
Workspace "1" o-- "0..*" ResourceSet
 | 
			
		||||
Peer -- Resource
 | 
			
		||||
RuleSet "1" o-- "0..*" Rule
 | 
			
		||||
SharedWorkspace -- RuleSet
 | 
			
		||||
 | 
			
		||||
UserWorkflows "1" o-- "0..*" Workflow
 | 
			
		||||
 | 
			
		||||
DatacenterWorkflows "1" o-- "0..*" Workflow
 | 
			
		||||
 | 
			
		||||
Ressource <|-- Data
 | 
			
		||||
Ressource <|-- Processing
 | 
			
		||||
Ressource <|-- Storage
 | 
			
		||||
Ressource <|-- Datacenter
 | 
			
		||||
Ressource <|-- Workflow
 | 
			
		||||
Resource<|-- DataResource
 | 
			
		||||
Resource<|-- ProcessingResource
 | 
			
		||||
Resource<|-- StorageResource
 | 
			
		||||
Resource<|-- ComputeResource
 | 
			
		||||
Resource<|-- Workflow
 | 
			
		||||
 | 
			
		||||
ResourceSet "1" o-- "0..*" Ressource
 | 
			
		||||
 | 
			
		||||
Workflow "1" o-- "0..*" ResourceSet
 | 
			
		||||
Workflow "1" o-- "0..*" WorkflowSchedule
 | 
			
		||||
Workflow "1" o-- "0..1" WorkflowSchedule
 | 
			
		||||
Workflow "1" o-- "0..*" Graph
 | 
			
		||||
 | 
			
		||||
Graph "1" o-- "0..*" Resources
 | 
			
		||||
Graph "1" o-- "0..*" Link
 | 
			
		||||
 | 
			
		||||
Ressource --o GraphicElement
 | 
			
		||||
Resource--o GraphicElement
 | 
			
		||||
Link -- GraphicLink
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/oclib.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/oclib.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 102 KiB  | 
							
								
								
									
										325
									
								
								doc/order_model.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								doc/order_model.puml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,325 @@
 | 
			
		||||
@startuml
 | 
			
		||||
 | 
			
		||||
class AbstractObject {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Name string
 | 
			
		||||
  IsDraft bool // is consider as a draft
 | 
			
		||||
  UpdateDate date 
 | 
			
		||||
  LastPeerWriter string 
 | 
			
		||||
  CreatorID string 
 | 
			
		||||
  AccessMode int // public or private
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AbstractObject ^-- AbstractResource
 | 
			
		||||
AbstractObject ^-- Order
 | 
			
		||||
AbstractObject ^-- Booking
 | 
			
		||||
AbstractObject ^-- BuyingStatus
 | 
			
		||||
AbstractObject ^-- WorkflowExecution
 | 
			
		||||
AbstractObject ^-- Workflow
 | 
			
		||||
 | 
			
		||||
class AbstractResource {
 | 
			
		||||
  Logo string 
 | 
			
		||||
  Description string 
 | 
			
		||||
  ShortDescription string 
 | 
			
		||||
  Owners []string 
 | 
			
		||||
  UsageRestrictions string 
 | 
			
		||||
  
 | 
			
		||||
  VerifyAuth(request) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AbstractResource "1 " --* "many  " ResourceInstanceITF
 | 
			
		||||
AbstractCustomizedResource "1  " --* "1  " ResourceInstanceITF
 | 
			
		||||
 | 
			
		||||
AbstractResource ^-- ComputeResource
 | 
			
		||||
AbstractResource ^-- DataResource
 | 
			
		||||
AbstractResource ^-- ProcessingResource
 | 
			
		||||
AbstractResource ^-- StorageResource
 | 
			
		||||
AbstractResource ^-- WorkflowResource
 | 
			
		||||
class ComputeResource {
 | 
			
		||||
  Architecture string 
 | 
			
		||||
  Infrastructure string
 | 
			
		||||
}
 | 
			
		||||
class DataResource {
 | 
			
		||||
  Type string 
 | 
			
		||||
  Quality string 
 | 
			
		||||
  OpenData bool 
 | 
			
		||||
  Static bool 
 | 
			
		||||
  UpdatePeriod date 
 | 
			
		||||
  PersonalData bool 
 | 
			
		||||
  AnonymizedPersonalData bool
 | 
			
		||||
  SizeGB float64
 | 
			
		||||
  Licence string 
 | 
			
		||||
  Example string
 | 
			
		||||
}
 | 
			
		||||
ProcessingResource "1  " *-- "1  " ProcessingUsage
 | 
			
		||||
class ProcessingUsage {
 | 
			
		||||
  CPUs map[string]CPU 
 | 
			
		||||
  GPUs map[string]GPU 
 | 
			
		||||
  RAM RAM
 | 
			
		||||
  StorageGB float64 
 | 
			
		||||
  Hypothesis string 
 | 
			
		||||
  ScalingModel string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ProcessingResource {
 | 
			
		||||
  Infrastructure string 
 | 
			
		||||
  Service bool 
 | 
			
		||||
  Usage ProcessingUsage
 | 
			
		||||
  OpenSource bool 
 | 
			
		||||
  License string 
 | 
			
		||||
  Maturity string 
 | 
			
		||||
}
 | 
			
		||||
class StorageResource {
 | 
			
		||||
  Type string 
 | 
			
		||||
  Accronym string
 | 
			
		||||
}
 | 
			
		||||
WorkflowResource "1  " --* "many  " ComputeResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " DataResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " ProcessingResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " StorageResource
 | 
			
		||||
class WorkflowResource {
 | 
			
		||||
  WorkflowID string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ExploitResourceSet {}
 | 
			
		||||
 | 
			
		||||
AbstractCustomizedResource --^ AbstractResource
 | 
			
		||||
AbstractCustomizedResource --* ExploitResourceSet
 | 
			
		||||
ExploitResourceSet ^-- CustomizedComputeResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedDataResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedProcessingResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedStorageResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedWorkflowResource
 | 
			
		||||
class AbstractCustomizedResource {
 | 
			
		||||
// A customized resource is an 
 | 
			
		||||
// extended abstract resource not use in catalog
 | 
			
		||||
  ExplicitBookingDurationS float64 
 | 
			
		||||
  UsageStart date 
 | 
			
		||||
  UsageEnd date 
 | 
			
		||||
  SelectedPricing string
 | 
			
		||||
}
 | 
			
		||||
class CustomizedComputeResource {
 | 
			
		||||
  CPUsLocated map[string]float64
 | 
			
		||||
  GPUsLocated map[string]float64
 | 
			
		||||
  RAMLocated float64
 | 
			
		||||
}
 | 
			
		||||
class CustomizedDataResource {
 | 
			
		||||
  StorageGB float64
 | 
			
		||||
}
 | 
			
		||||
class CustomizedProcessingResource {
 | 
			
		||||
  Container Container
 | 
			
		||||
}
 | 
			
		||||
class CustomizedStorageResource {
 | 
			
		||||
  StorageGB bool
 | 
			
		||||
}
 | 
			
		||||
class CustomizedWorkflowResource {}
 | 
			
		||||
 | 
			
		||||
interface ResourceInstanceITF {
 | 
			
		||||
  GetID() string 
 | 
			
		||||
  VerifyPartnership() bool // eval if there is one partnership per peer groups in every instance
 | 
			
		||||
  GetPeerGroups() []ResourcePartnerITF, []map[string][]string 
 | 
			
		||||
  ClearPeerGroups()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourceInstanceITF -- ResourceInstance
 | 
			
		||||
ResourceInstance ^-- ComputeResourceInstance
 | 
			
		||||
ResourceInstance ^-- StorageResourceInstance
 | 
			
		||||
ResourceInstance "many  " --* "1  " ResourcePartnerITF
 | 
			
		||||
class ResourceInstance {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Location Geopoint
 | 
			
		||||
  Country CountryCode 
 | 
			
		||||
  AccessProtocol string 
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourceInstance {
 | 
			
		||||
  SecurityLevel string 
 | 
			
		||||
  PowerSource string 
 | 
			
		||||
  CPUs map[string]CPU 
 | 
			
		||||
  GPUs map[string]GPU 
 | 
			
		||||
  RAM RAM
 | 
			
		||||
}
 | 
			
		||||
class StorageResourceInstance {
 | 
			
		||||
  Local bool 
 | 
			
		||||
  SecurityLevel string 
 | 
			
		||||
  SizeType string 
 | 
			
		||||
  SizeGB int 
 | 
			
		||||
  Encryption bool 
 | 
			
		||||
  Redundancy string 
 | 
			
		||||
  Throughput string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourcePartnerITF -- ResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- ComputeResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- DataResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- StorageResourcePartnership
 | 
			
		||||
 | 
			
		||||
interface ResourcePartnerITF {
 | 
			
		||||
  GetPricing(id string) PricingProfileITF
 | 
			
		||||
  GetPeerGroups() []ResourcePartnerITF, []map[string][]string 
 | 
			
		||||
  ClearPeerGroups()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourcePartnership "many  " --*  "1  " PricingProfileITF
 | 
			
		||||
class ResourcePartnership{
 | 
			
		||||
  Namespace string 
 | 
			
		||||
  PeerGroups map[string][]string 
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourcePartnership {
 | 
			
		||||
  MaxAllowedCPUsCores map[string]int 
 | 
			
		||||
  MaxAllowedGPUsMemoryGB map[string]float64 
 | 
			
		||||
  RAMSizeGB float64
 | 
			
		||||
}
 | 
			
		||||
class DataResourcePartnership {
 | 
			
		||||
  MaxDownloadableGBAllowed float64
 | 
			
		||||
  PersonalDataAllowed bool
 | 
			
		||||
  AnonymizedPersonalDataAllowed bool
 | 
			
		||||
}
 | 
			
		||||
class StorageResourcePartnership {
 | 
			
		||||
  MaxSizeGBAllowed float64
 | 
			
		||||
  OnlyEncryptedAllowed bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RefundType -- AccessPricingProfile
 | 
			
		||||
enum RefundType {
 | 
			
		||||
  REFUND_DEAD_END
 | 
			
		||||
  REFUND_ON_ERROR
 | 
			
		||||
  REFUND_ON_EARLY_END
 | 
			
		||||
}
 | 
			
		||||
PricingProfileITF -- AccessPricingProfile
 | 
			
		||||
PricingProfileITF -- ExploitPricingProfile
 | 
			
		||||
PricingProfileITF -- WorkflowResourcePricingProfile
 | 
			
		||||
AccessPricingProfile ^-- DataResourcePricingProfile
 | 
			
		||||
AccessPricingProfile ^-- ProcessingResourcePricingProfile
 | 
			
		||||
ExploitPricingProfile ^-- ComputeResourcePricingProfile
 | 
			
		||||
ExploitPricingProfile ^-- StorageResourcePricingProfile
 | 
			
		||||
interface PricingProfileITF {
 | 
			
		||||
  GetPrice(quantity float64, val float64, start date, end date, request) float64
 | 
			
		||||
  IsPurchased() bool
 | 
			
		||||
}
 | 
			
		||||
class AccessPricingProfile {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Pricing PricingStrategy
 | 
			
		||||
  DefaultRefundType RefundType 
 | 
			
		||||
  RefundRatio int // percentage of refund on price
 | 
			
		||||
}
 | 
			
		||||
class DataResourcePricingProfile {}
 | 
			
		||||
class ProcessingResourcePricingProfile {}
 | 
			
		||||
 | 
			
		||||
ExploitPrivilegeStrategy -- ExploitPricingProfile
 | 
			
		||||
enum ExploitPrivilegeStrategy {
 | 
			
		||||
  BASIC
 | 
			
		||||
  GARANTED_ON_DELAY
 | 
			
		||||
  GARANTED
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
AccessPricingProfile --* PricingStrategy
 | 
			
		||||
AccessPricingProfile ^-- ExploitPricingProfile
 | 
			
		||||
class ExploitPricingProfile {
 | 
			
		||||
  AdditionnalRefundTypes RefundTypeint 
 | 
			
		||||
  PrivilegeStrategy ExploitPrivilegeStrategy 
 | 
			
		||||
  GarantedDelaySecond int 
 | 
			
		||||
  Exceeding bool 
 | 
			
		||||
  ExceedingRatio int // percentage of Exceeding based on price
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourcePricingProfile {
 | 
			
		||||
  OverrideCPUsPrices map[string]float64
 | 
			
		||||
  OverrideGPUsPrices map[string]float64
 | 
			
		||||
  OverrideRAMPrice float64
 | 
			
		||||
}
 | 
			
		||||
class StorageResourcePricingProfile {}
 | 
			
		||||
WorkflowResourcePricingProfile "1  " --* "many  " ExploitResourceSet
 | 
			
		||||
 | 
			
		||||
class WorkflowResourcePricingProfile {
 | 
			
		||||
  ID string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BuyingStrategy -- PricingStrategy
 | 
			
		||||
enum BuyingStrategy {
 | 
			
		||||
  UNLIMITED 
 | 
			
		||||
  SUBSCRIPTION 
 | 
			
		||||
  PAY_PER_USE
 | 
			
		||||
}
 | 
			
		||||
Strategy -- TimePricingStrategy
 | 
			
		||||
Strategy "0-1  " *-- " " PricingStrategy
 | 
			
		||||
interface Strategy {
 | 
			
		||||
  GetStrategy () string 
 | 
			
		||||
  GetStrategyValue() int
 | 
			
		||||
}
 | 
			
		||||
enum TimePricingStrategy {
 | 
			
		||||
  ONCE 
 | 
			
		||||
  PER_SECOND 
 | 
			
		||||
  PER_MINUTE
 | 
			
		||||
  PER_HOUR
 | 
			
		||||
  PER_DAY
 | 
			
		||||
  PER_WEEK
 | 
			
		||||
  PER_MONTH
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PricingStrategy {
 | 
			
		||||
  Price float64 
 | 
			
		||||
  BuyingStrategy 
 | 
			
		||||
  TimePricingStrategy TimePricingStrategy
 | 
			
		||||
  OverrideStrategy Strategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PeerOrder "many  " *-- "1  " Order
 | 
			
		||||
PeerItemOrder "many  " *-- "1  " PeerOrder
 | 
			
		||||
PricedItemITF "many  " *-- "1  " PeerItemOrder
 | 
			
		||||
 | 
			
		||||
PricedItemITF -- AbstractCustomizedResource
 | 
			
		||||
 | 
			
		||||
class Order {
 | 
			
		||||
  OrderBy string 
 | 
			
		||||
  WorkflowExecutionIDs []string
 | 
			
		||||
  Status string 
 | 
			
		||||
  Total float64
 | 
			
		||||
}
 | 
			
		||||
class PeerOrder {
 | 
			
		||||
  PeerID string 
 | 
			
		||||
  Error string 
 | 
			
		||||
  Status string 
 | 
			
		||||
  BillingAddress string 
 | 
			
		||||
  Total float64
 | 
			
		||||
}
 | 
			
		||||
class PeerItemOrder {
 | 
			
		||||
  Quantity int 
 | 
			
		||||
  BuyingStatus string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BuyingStatus {}
 | 
			
		||||
 | 
			
		||||
WorkflowExecution "many  " --* "1  " Workflow
 | 
			
		||||
Workflow "1  " --* "many  " WorkflowScheduler
 | 
			
		||||
WorkflowScheduler "1  " --* "many  " WorkflowExecution
 | 
			
		||||
 | 
			
		||||
class WorkflowExecution {
 | 
			
		||||
  ExecDate date 
 | 
			
		||||
  EndDate date 
 | 
			
		||||
  State string 
 | 
			
		||||
  WorkflowID string 
 | 
			
		||||
  
 | 
			
		||||
  ToBookings() []Booking
 | 
			
		||||
}
 | 
			
		||||
class WorkflowScheduler* {
 | 
			
		||||
  Message string 
 | 
			
		||||
  Warning string 
 | 
			
		||||
  Start date 
 | 
			
		||||
  End date 
 | 
			
		||||
  DurationS float64
 | 
			
		||||
  Cron string
 | 
			
		||||
  
 | 
			
		||||
  Schedules(workflowID string, request) []WorkflowExecution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Workflow "1  " --* "many  " ExploitResourceSet
 | 
			
		||||
 | 
			
		||||
class Workflow {}
 | 
			
		||||
 | 
			
		||||
interface PricedItemITF {
 | 
			
		||||
  getPrice(request) float64, error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@enduml
 | 
			
		||||
							
								
								
									
										29
									
								
								doc/paymentflowV1.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								doc/paymentflowV1.puml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
@startuml
 | 
			
		||||
user -> client : schedule 
 | 
			
		||||
client -> OrderAPIP1 : check book
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : check book
 | 
			
		||||
datacenterAPIP2 -> OrderAPIP1 : send ok
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : generate draft book
 | 
			
		||||
OrderAPIP1 -> client : send ok 
 | 
			
		||||
client -> OrderAPIP1 : send scheduler
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : draft executions
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : draft order
 | 
			
		||||
OrderAPIP1 -> client : send drafted order
 | 
			
		||||
client -> user : 
 | 
			
		||||
user -> client : select pricing profile
 | 
			
		||||
client -> OrderAPIP1 : update order 
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : check book
 | 
			
		||||
datacenterAPIP2 -> OrderAPIP1 : send ok
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : generate draft book
 | 
			
		||||
OrderAPIP1 -> client : send order
 | 
			
		||||
user -> client : order
 | 
			
		||||
client -> OrderAPIP1 : order 
 | 
			
		||||
OrderAPIP1 -> PaymentAPIBCP1 : send payment
 | 
			
		||||
PaymentAPIBCP1 -> OrderAPIP1 : send ok 
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : undraft booking
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : undraft execution
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : undraft order
 | 
			
		||||
OrderAPIP1 -> client : send ok 
 | 
			
		||||
client -> client : redirect
 | 
			
		||||
@enduml
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										599
									
								
								entrypoint.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										599
									
								
								entrypoint.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,599 @@
 | 
			
		||||
package oclib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/live"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	beego "github.com/beego/beego/v2/server/web"
 | 
			
		||||
	"github.com/beego/beego/v2/server/web/context"
 | 
			
		||||
	"github.com/goraz/onion"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Filters = dbs.Filters
 | 
			
		||||
 | 
			
		||||
type LibDataEnum int
 | 
			
		||||
 | 
			
		||||
// init accessible constant to retrieve data from the database
 | 
			
		||||
const (
 | 
			
		||||
	INVALID             LibDataEnum = iota
 | 
			
		||||
	DATA_RESOURCE                   = tools.DATA_RESOURCE
 | 
			
		||||
	PROCESSING_RESOURCE             = tools.PROCESSING_RESOURCE
 | 
			
		||||
	STORAGE_RESOURCE                = tools.STORAGE_RESOURCE
 | 
			
		||||
	COMPUTE_RESOURCE                = tools.COMPUTE_RESOURCE
 | 
			
		||||
	WORKFLOW_RESOURCE               = tools.WORKFLOW_RESOURCE
 | 
			
		||||
	WORKFLOW                        = tools.WORKFLOW
 | 
			
		||||
	WORKSPACE                       = tools.WORKSPACE
 | 
			
		||||
	WORKFLOW_EXECUTION              = tools.WORKFLOW_EXECUTION
 | 
			
		||||
	PEER                            = tools.PEER
 | 
			
		||||
	COLLABORATIVE_AREA              = tools.COLLABORATIVE_AREA
 | 
			
		||||
	RULE                            = tools.RULE
 | 
			
		||||
	BOOKING                         = tools.BOOKING
 | 
			
		||||
	ORDER                           = tools.ORDER
 | 
			
		||||
	LIVE_DATACENTER                 = tools.LIVE_DATACENTER
 | 
			
		||||
	LIVE_STORAGE                    = tools.LIVE_STORAGE
 | 
			
		||||
	PURCHASE_RESOURCE               = tools.PURCHASE_RESOURCE
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// will turn into standards api hostnames
 | 
			
		||||
func (d LibDataEnum) API() string {
 | 
			
		||||
	return tools.DefaultAPI[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// will turn into standards name
 | 
			
		||||
func (d LibDataEnum) String() string {
 | 
			
		||||
	return tools.Str[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// will turn into enum index
 | 
			
		||||
func (d LibDataEnum) EnumIndex() int {
 | 
			
		||||
	return int(d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsQueryParamsEquals(input *context.BeegoInput, name string, val interface{}) bool {
 | 
			
		||||
	path := strings.Split(input.URI(), "?")
 | 
			
		||||
	if len(path) >= 2 {
 | 
			
		||||
		uri := strings.Split(path[1], "&")
 | 
			
		||||
		for _, val := range uri {
 | 
			
		||||
			kv := strings.Split(val, "=")
 | 
			
		||||
			if kv[0] == name && fmt.Sprintf("%v", val) == kv[1] {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// model to define the shallow data structure
 | 
			
		||||
type LibDataShallow struct {
 | 
			
		||||
	Data []utils.ShallowDBObject `bson:"data" json:"data"`
 | 
			
		||||
	Code int                     `bson:"code" json:"code"`
 | 
			
		||||
	Err  string                  `bson:"error" json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// model to define the data structure
 | 
			
		||||
type LibData struct {
 | 
			
		||||
	Data utils.DBObject `bson:"data" json:"data"`
 | 
			
		||||
	Code int            `bson:"code" json:"code"`
 | 
			
		||||
	Err  string         `bson:"error" json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func InitDaemon(appName string) {
 | 
			
		||||
	config.SetAppName(appName) // set the app name to the logger to define the main log chan
 | 
			
		||||
	// create a temporary console logger for init
 | 
			
		||||
	logs.SetLogger(logs.CreateLogger("main"))
 | 
			
		||||
	// Load the right config file
 | 
			
		||||
	o := GetConfLoader()
 | 
			
		||||
 | 
			
		||||
	// feed the library with the loaded config
 | 
			
		||||
	SetConfig(
 | 
			
		||||
		o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"),
 | 
			
		||||
		o.GetStringDefault("MONGO_DATABASE", "DC_myDC"),
 | 
			
		||||
		o.GetStringDefault("NATS_URL", "nats://localhost:4222"),
 | 
			
		||||
		o.GetStringDefault("LOKI_URL", ""),
 | 
			
		||||
		o.GetStringDefault("LOG_LEVEL", "info"),
 | 
			
		||||
	)
 | 
			
		||||
	// Beego init
 | 
			
		||||
	beego.BConfig.AppName = appName
 | 
			
		||||
	beego.BConfig.Listen.HTTPPort = o.GetIntDefault("port", 8080)
 | 
			
		||||
	beego.BConfig.WebConfig.DirectoryIndex = true
 | 
			
		||||
	beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IDTokenClaims struct {
 | 
			
		||||
	UserID string   `json:"user_id"`
 | 
			
		||||
	PeerID string   `json:"peer_id"`
 | 
			
		||||
	Groups []string `json:"groups"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SessionClaims struct
 | 
			
		||||
type SessionClaims struct {
 | 
			
		||||
	AccessToken map[string]interface{} `json:"access_token"`
 | 
			
		||||
	IDToken     IDTokenClaims          `json:"id_token"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Claims struct
 | 
			
		||||
type Claims struct {
 | 
			
		||||
	Session SessionClaims `json:"session"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExtractTokenInfo(request http.Request) (string, string, []string) {
 | 
			
		||||
	reqToken := request.Header.Get("Authorization")
 | 
			
		||||
	splitToken := strings.Split(reqToken, "Bearer ")
 | 
			
		||||
	if len(splitToken) < 2 {
 | 
			
		||||
		reqToken = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		reqToken = splitToken[1]
 | 
			
		||||
	}
 | 
			
		||||
	if reqToken != "" {
 | 
			
		||||
		token := strings.Split(reqToken, ".")
 | 
			
		||||
		if len(token) > 2 {
 | 
			
		||||
			bytes, err := base64.StdEncoding.DecodeString(token[2])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", []string{}
 | 
			
		||||
			}
 | 
			
		||||
			var c Claims
 | 
			
		||||
			err = json.Unmarshal(bytes, &c)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", []string{}
 | 
			
		||||
			}
 | 
			
		||||
			return c.Session.IDToken.UserID, c.Session.IDToken.PeerID, c.Session.IDToken.Groups
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", "", []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Init(appName string) {
 | 
			
		||||
	InitDaemon(appName)
 | 
			
		||||
	api := &tools.API{}
 | 
			
		||||
	api.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Expose subpackages
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
/* GetLogger returns the main logger
 | 
			
		||||
* @return zerolog.Logger
 | 
			
		||||
 */
 | 
			
		||||
func GetLogger() zerolog.Logger {
 | 
			
		||||
	return logs.GetLogger()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* SetConfig will set the config and create a logger according to app configuration and initialize mongo accessor
 | 
			
		||||
* @param url string
 | 
			
		||||
* @param database string
 | 
			
		||||
* @param natsUrl string
 | 
			
		||||
* @param lokiUrl string
 | 
			
		||||
* @param logLevel string
 | 
			
		||||
* @return *Config
 | 
			
		||||
 */
 | 
			
		||||
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *config.Config {
 | 
			
		||||
	cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Init : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			fmt.Printf("Panic recovered in Init : %v - %v\n", r, string(debug.Stack()))
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	logs.CreateLogger("main")
 | 
			
		||||
	mongo.MONGOService.Init(models.GetModelsNames(), config.GetConfig()) // init the mongo service
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* GetConfig will get the config
 | 
			
		||||
* @return *Config
 | 
			
		||||
 */
 | 
			
		||||
func GetConfig() *config.Config {
 | 
			
		||||
	return config.GetConfig()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* GetConfLoader
 | 
			
		||||
* Get the configuration loader for the application
 | 
			
		||||
* Parameters:
 | 
			
		||||
* - AppName: string : the name of the application
 | 
			
		||||
* Returns:
 | 
			
		||||
* - *onion.Onion : the configuration loader
 | 
			
		||||
*  The configuration loader will load the configuration from the following sources:
 | 
			
		||||
*  - the environment variables with the prefix OCAPPNAME_
 | 
			
		||||
*  - the file /etc/oc/appname.json
 | 
			
		||||
*  - the file ./appname.json
 | 
			
		||||
*  The configuration loader will merge the configuration from the different sources
 | 
			
		||||
*  The configuration loader will give priority to the environment variables
 | 
			
		||||
*  The configuration loader will give priority to the local file over the default file
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func GetConfLoader() *onion.Onion {
 | 
			
		||||
	return config.GetConfLoader()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Request struct {
 | 
			
		||||
	collection LibDataEnum
 | 
			
		||||
	user       string
 | 
			
		||||
	peerID     string
 | 
			
		||||
	groups     []string
 | 
			
		||||
	caller     *tools.HTTPCaller
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRequest(collection LibDataEnum, user string, peerID string, groups []string, caller *tools.HTTPCaller) *Request {
 | 
			
		||||
	return &Request{collection: collection, user: user, peerID: peerID, groups: groups, caller: caller}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToScheduler(m interface{}) (n *workflow_execution.WorkflowSchedule) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return m.(*workflow_execution.WorkflowSchedule)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) Schedule(wfID string, scheduler *workflow_execution.WorkflowSchedule) (*workflow_execution.WorkflowSchedule, error) {
 | 
			
		||||
	ws, _, _, err := scheduler.Schedules(wfID, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return ws, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) CheckBooking(wfID string, start string, end string, durationInS float64, cron string) bool {
 | 
			
		||||
	ok, _, _, _, _, err := workflow_execution.NewScheduler(start, end, durationInS, cron).GetBuyAndBook(wfID, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error {
 | 
			
		||||
	/*return o.Pay(scheduler, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})*/
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Search will search for the data in the database
 | 
			
		||||
* @param filters *dbs.Filters
 | 
			
		||||
* @param word string
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibDataShallow
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) Search(filters *dbs.Filters, word string, isDraft bool) (data LibDataShallow) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Search : "+fmt.Sprintf("%v", r)))
 | 
			
		||||
			data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).Search(filters, word, isDraft)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibDataShallow{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LoadAll will load all the data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibDataShallow
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) LoadAll(isDraft bool) (data LibDataShallow) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadAll : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).LoadAll(isDraft)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibDataShallow{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LoadOne will load one data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param id string
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) LoadOne(id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibData{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* UpdateOne will update one data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param set map[string]interface{}
 | 
			
		||||
* @param id string
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in UpdateOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).UpdateOne(model.Deserialize(set, model), id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibData{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* DeleteOne will delete one data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param id string
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) DeleteOne(id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in DeleteOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).DeleteOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibData{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* StoreOne will store one data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param object map[string]interface{}
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) StoreOne(object map[string]interface{}) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in StoreOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in StoreOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).StoreOne(model.Deserialize(object, model))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibData{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* CopyOne will copy one data from the database
 | 
			
		||||
* @param collection LibDataEnum
 | 
			
		||||
* @param object map[string]interface{}
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func (r *Request) CopyOne(object map[string]interface{}) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in CopyOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).CopyOne(model.Deserialize(object, model))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data = LibData{Data: d, Code: code}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ================ CAST ========================= //
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToDataResource() *resources.DataResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.DATA_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.DataResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToComputeResource() *resources.ComputeResource {
 | 
			
		||||
	if l.Data != nil && l.Data.GetAccessor(nil).GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.ComputeResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToStorageResource() *resources.StorageResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.STORAGE_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.StorageResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToProcessingResource() *resources.ProcessingResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PROCESSING_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.ProcessingResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToWorkflowResource() *resources.WorkflowResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.WorkflowResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToPeer() *peer.Peer {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PEER {
 | 
			
		||||
		return l.Data.(*peer.Peer)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToWorkflow() *w2.Workflow {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW {
 | 
			
		||||
		return l.Data.(*w2.Workflow)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToWorkspace() *workspace.Workspace {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKSPACE {
 | 
			
		||||
		return l.Data.(*workspace.Workspace)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToCollaborativeArea() *collaborative_area.CollaborativeArea {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
 | 
			
		||||
		return l.Data.(*collaborative_area.CollaborativeArea)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToRule() *rule.Rule {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
 | 
			
		||||
		return l.Data.(*rule.Rule)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecution {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_EXECUTION {
 | 
			
		||||
		return l.Data.(*workflow_execution.WorkflowExecution)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToOrder() *order.Order {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.ORDER {
 | 
			
		||||
		return l.Data.(*order.Order)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToLiveDatacenter() *live.LiveDatacenter {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.LIVE_DATACENTER {
 | 
			
		||||
		return l.Data.(*live.LiveDatacenter)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToLiveStorage() *live.LiveStorage {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.LIVE_STORAGE {
 | 
			
		||||
		return l.Data.(*live.LiveStorage)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToBookings() *booking.Booking {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.BOOKING {
 | 
			
		||||
		return l.Data.(*booking.Booking)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PURCHASE_RESOURCE {
 | 
			
		||||
		return l.Data.(*purchase_resource.PurchaseResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								go.mod
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										58
									
								
								go.mod
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,11 +1,61 @@
 | 
			
		||||
module oc-lib
 | 
			
		||||
module cloud.o-forge.io/core/oc-lib
 | 
			
		||||
 | 
			
		||||
go 1.22.0
 | 
			
		||||
 | 
			
		||||
require github.com/rs/zerolog v1.33.0
 | 
			
		||||
require (
 | 
			
		||||
	github.com/beego/beego/v2 v2.3.1
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.22.0
 | 
			
		||||
	github.com/google/uuid v1.6.0
 | 
			
		||||
	github.com/goraz/onion v0.1.3
 | 
			
		||||
	github.com/nats-io/nats.go v1.37.0
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/rs/zerolog v1.33.0
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/nats-io/nkeys v0.4.7 // indirect
 | 
			
		||||
	github.com/nats-io/nuid v1.0.1 // indirect
 | 
			
		||||
	github.com/stretchr/objx v0.5.2 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.13 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.19 // indirect
 | 
			
		||||
	golang.org/x/sys v0.12.0 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.16.0
 | 
			
		||||
	golang.org/x/sys v0.22.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
			
		||||
	github.com/biter777/countries v1.7.5
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.4 // indirect
 | 
			
		||||
	github.com/go-playground/locales v0.14.1 // indirect
 | 
			
		||||
	github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/hashicorp/golang-lru v0.5.4 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.17.9 // indirect
 | 
			
		||||
	github.com/kr/text v0.1.0 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.7.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.19.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.5.0 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.48.0 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.12.0 // indirect
 | 
			
		||||
	github.com/robfig/cron v1.2.0
 | 
			
		||||
	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 | 
			
		||||
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
			
		||||
	github.com/xdg-go/scram v1.1.2 // indirect
 | 
			
		||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
			
		||||
	github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.25.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.27.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.16.0 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.34.2 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										166
									
								
								go.sum
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										166
									
								
								go.sum
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,15 +1,177 @@
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/beego/beego/v2 v2.3.1 h1:7MUKMpJYzOXtCUsTEoXOxsDV/UcHw6CPbaWMlthVNsc=
 | 
			
		||||
github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4=
 | 
			
		||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
			
		||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
			
		||||
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
 | 
			
		||||
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
			
		||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 | 
			
		||||
github.com/etcd-io/etcd v3.3.17+incompatible/go.mod h1:cdZ77EstHBwVtD6iTgzgvogwcjo9m4iOqoijouPJ4bs=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 | 
			
		||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 | 
			
		||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 | 
			
		||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
			
		||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 | 
			
		||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
			
		||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
			
		||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/goraz/onion v0.1.3 h1:KhyvbDA2b70gcz/d5izfwTiOH8SmrvV43AsVzpng3n0=
 | 
			
		||||
github.com/goraz/onion v0.1.3/go.mod h1:XEmz1XoBz+wxTgWB8NwuvRm4RAu3vKxvrmYtzK+XCuQ=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 | 
			
		||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 | 
			
		||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
			
		||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
 | 
			
		||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 | 
			
		||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
			
		||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 | 
			
		||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 | 
			
		||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
 | 
			
		||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 | 
			
		||||
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
 | 
			
		||||
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
 | 
			
		||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
 | 
			
		||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
 | 
			
		||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 | 
			
		||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 | 
			
		||||
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
 | 
			
		||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
 | 
			
		||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
 | 
			
		||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 | 
			
		||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
 | 
			
		||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
 | 
			
		||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
 | 
			
		||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 | 
			
		||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 | 
			
		||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
 | 
			
		||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 | 
			
		||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 | 
			
		||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
 | 
			
		||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 | 
			
		||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
 | 
			
		||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 | 
			
		||||
github.com/skarademir/naturalsort v0.0.0-20150715044055-69a5d87bef62/go.mod h1:oIdVclZaltY1Nf7OQUkg1/2jImBJ+ZfKZuDIRSwk3p0=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 | 
			
		||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
			
		||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
			
		||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 | 
			
		||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 | 
			
		||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 | 
			
		||||
github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 h1:tBiBTKHnIjovYoLX/TPkcf+OjqqKGQrPtGT3Foz+Pgo=
 | 
			
		||||
github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dhkt//vS29v3zpbEwoa+zb2Cn5xj5uO4K5U=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
 | 
			
		||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
 | 
			
		||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 | 
			
		||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
 | 
			
		||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
 | 
			
		||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 | 
			
		||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
 | 
			
		||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 | 
			
		||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,43 @@
 | 
			
		||||
package oclib
 | 
			
		||||
package logs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"oc-lib/logs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var logger zerolog.Logger
 | 
			
		||||
 | 
			
		||||
// CreateLogger
 | 
			
		||||
// logs.CreateLogger
 | 
			
		||||
// Create a new logger
 | 
			
		||||
// Parameters:
 | 
			
		||||
// - appname: string : the name of the application using oclib
 | 
			
		||||
// - url: string : the url of a loki logger, console log only if ""
 | 
			
		||||
// Returns:
 | 
			
		||||
// - zerolog.Logger : the logger that will log for the library and the app
 | 
			
		||||
func CreateLogger(appname string, url string) zerolog.Logger {
 | 
			
		||||
 | 
			
		||||
func GetLogger() zerolog.Logger {
 | 
			
		||||
	return logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetLogger(l zerolog.Logger) {
 | 
			
		||||
	logger = l
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateLogger(funcName string) zerolog.Logger {
 | 
			
		||||
	url := config.GetConfig().LokiUrl
 | 
			
		||||
	if url != "" {
 | 
			
		||||
		labels := map[string]string{
 | 
			
		||||
			"app":      "app",
 | 
			
		||||
			"app":      config.GetAppName(),
 | 
			
		||||
			"code":     "go",
 | 
			
		||||
			"platform": runtime.GOOS,
 | 
			
		||||
			"function": funcName,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lokiWriter := logs.NewLokiWriter(url, labels)
 | 
			
		||||
		lokiWriter := NewLokiWriter(url, labels)
 | 
			
		||||
 | 
			
		||||
		consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
 | 
			
		||||
 | 
			
		||||
@@ -55,6 +55,17 @@ func (w *LokiWriter) Write(p []byte) (n int, err error) {
 | 
			
		||||
	}
 | 
			
		||||
	labels["level"] = level
 | 
			
		||||
 | 
			
		||||
	// Add label that have been added to the event
 | 
			
		||||
	// A bit unsafe since we don't know what could be stored in the event
 | 
			
		||||
	// but we can't access this object once passed to the multilevel writter
 | 
			
		||||
	
 | 
			
		||||
	for k,v := range(event){
 | 
			
		||||
		if k != "level" && k != "time" && k != "message"{
 | 
			
		||||
			labels[k] = v.(string)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// Format the timestamp in nanoseconds
 | 
			
		||||
	timestamp := fmt.Sprintf("%d000000", time.Now().UnixNano()/int64(time.Millisecond))
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +87,7 @@ func (w *LokiWriter) Write(p []byte) (n int, err error) {
 | 
			
		||||
 | 
			
		||||
	//fmt.Printf("Sending payload to Loki: %s\n", string(payloadBytes))
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest("POST", w.url, bytes.NewReader(payloadBytes))
 | 
			
		||||
	req, err := http.NewRequest("POST", w.url + "/loki/api/v1/push", bytes.NewReader(payloadBytes))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, fmt.Errorf("failed to create HTTP request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										202
									
								
								models/bill/bill.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								models/bill/bill.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
package bill
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type Bill struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	OrderID   string                `json:"order_id" bson:"order_id" validate:"required"`
 | 
			
		||||
	Status    enum.CompletionStatus `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	SubOrders map[string]*PeerOrder `json:"sub_orders" bson:"sub_orders"`
 | 
			
		||||
	Total     float64               `json:"total" bson:"total" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenerateBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	// hhmmm : should get... the loop.
 | 
			
		||||
	return &Bill{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			Name:    "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
 | 
			
		||||
			IsDraft: false,
 | 
			
		||||
		},
 | 
			
		||||
		OrderID: order.UUID,
 | 
			
		||||
		Status:  enum.PENDING,
 | 
			
		||||
		// SubOrders: peerOrders,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DraftFirstBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	peers := map[string][]*PeerItemOrder{}
 | 
			
		||||
	for _, p := range order.Purchases {
 | 
			
		||||
		// TODO : if once
 | 
			
		||||
		if _, ok := peers[p.DestPeerID]; !ok {
 | 
			
		||||
			peers[p.DestPeerID] = []*PeerItemOrder{}
 | 
			
		||||
		}
 | 
			
		||||
		peers[p.DestPeerID] = append(peers[p.DestPeerID], &PeerItemOrder{
 | 
			
		||||
			Purchase: p,
 | 
			
		||||
			Item:     p.PricedItem,
 | 
			
		||||
			Quantity: 1,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for _, b := range order.Bookings {
 | 
			
		||||
		// TODO : if once
 | 
			
		||||
		isPurchased := false
 | 
			
		||||
		for _, p := range order.Purchases {
 | 
			
		||||
			if p.ResourceID == b.ResourceID {
 | 
			
		||||
				isPurchased = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if isPurchased {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := peers[b.DestPeerID]; !ok {
 | 
			
		||||
			peers[b.DestPeerID] = []*PeerItemOrder{}
 | 
			
		||||
		}
 | 
			
		||||
		peers[b.DestPeerID] = append(peers[b.DestPeerID], &PeerItemOrder{
 | 
			
		||||
			Item: b.PricedItem,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	peerOrders := map[string]*PeerOrder{}
 | 
			
		||||
	for peerID, items := range peers {
 | 
			
		||||
		pr, _, err := peer.NewAccessor(request).LoadOne(peerID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		peerOrders[peerID] = &PeerOrder{
 | 
			
		||||
			PeerID:         peerID,
 | 
			
		||||
			BillingAddress: pr.(*peer.Peer).WalletAddress,
 | 
			
		||||
			Items:          items,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	bill := &Bill{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			Name:    "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
 | 
			
		||||
			IsDraft: true,
 | 
			
		||||
		},
 | 
			
		||||
		OrderID:   order.UUID,
 | 
			
		||||
		Status:    enum.PENDING,
 | 
			
		||||
		SubOrders: peerOrders,
 | 
			
		||||
	}
 | 
			
		||||
	return bill.SumUpBill(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Bill) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.Status != set.(*Bill).Status {
 | 
			
		||||
		return true, &Bill{Status: set.(*Bill).Status} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Bill) SumUpBill(request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	for _, b := range d.SubOrders {
 | 
			
		||||
		err := b.SumUpBill(request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return d, err
 | 
			
		||||
		}
 | 
			
		||||
		d.Total += b.Total
 | 
			
		||||
	}
 | 
			
		||||
	return d, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PeerOrder struct {
 | 
			
		||||
	Error          string                `json:"error,omitempty" bson:"error,omitempty"`
 | 
			
		||||
	PeerID         string                `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
 | 
			
		||||
	Status         enum.CompletionStatus `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	BillingAddress string                `json:"billing_address,omitempty" bson:"billing_address,omitempty"`
 | 
			
		||||
	Items          []*PeerItemOrder      `json:"items,omitempty" bson:"items,omitempty"`
 | 
			
		||||
	Total          float64               `json:"total,omitempty" bson:"total,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) {
 | 
			
		||||
	d.Status = enum.PENDING
 | 
			
		||||
	go func() {
 | 
			
		||||
		// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
 | 
			
		||||
		d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
 | 
			
		||||
		if d.Status == enum.PAID {
 | 
			
		||||
			for _, b := range d.Items {
 | 
			
		||||
				if !b.Item.IsPurchasable() {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				accessor := purchase_resource.NewAccessor(request)
 | 
			
		||||
				accessor.StoreOne(&purchase_resource.PurchaseResource{
 | 
			
		||||
					ResourceID:   b.Item.GetID(),
 | 
			
		||||
					ResourceType: b.Item.GetType(),
 | 
			
		||||
					EndDate:      b.Item.GetLocationEnd(),
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if d.Status != enum.PENDING {
 | 
			
		||||
			response <- d
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
 | 
			
		||||
	for _, b := range d.Items {
 | 
			
		||||
		tot, err := b.GetPrice(request) // missing something
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		d.Total += tot
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PeerItemOrder struct {
 | 
			
		||||
	Quantity int                                 `json:"quantity,omitempty" bson:"quantity,omitempty"`
 | 
			
		||||
	Purchase *purchase_resource.PurchaseResource `json:"purchase,omitempty" bson:"purchase,omitempty"`
 | 
			
		||||
	Item     pricing.PricedItemITF               `json:"item,omitempty" bson:"item,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
 | 
			
		||||
	accessor := purchase_resource.NewAccessor(request)
 | 
			
		||||
	search, code, _ := accessor.Search(&dbs.Filters{
 | 
			
		||||
		And: map[string][]dbs.Filter{
 | 
			
		||||
			"resource_id": {{Operator: dbs.EQUAL.String(), Value: d.Item.GetID()}},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", d.Purchase.IsDraft)
 | 
			
		||||
	if code == 200 && len(search) > 0 {
 | 
			
		||||
		for _, s := range search {
 | 
			
		||||
			if s.(*purchase_resource.PurchaseResource).EndDate == nil || time.Now().UTC().After(*s.(*purchase_resource.PurchaseResource).EndDate) {
 | 
			
		||||
				return 0, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	p, err := d.Item.GetPrice()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return p * float64(d.Quantity), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WTF HOW TO SELECT THE RIGHT PRICE ???
 | 
			
		||||
// SHOULD SET A BUYING STATUS WHEN PAYMENT IS VALIDATED
 | 
			
		||||
							
								
								
									
										63
									
								
								models/bill/bill_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								models/bill/bill_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
package bill
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type billMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the billMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *billMongoAccessor {
 | 
			
		||||
	return &billMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.LIVE_DATACENTER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.LIVE_DATACENTER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *billMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// should verify if a source is existing...
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Bill{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Bill), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Bill), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Bill](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Bill](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Bill](filters, search, (&Bill{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								models/billing_process.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								models/billing_process.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# Billing process 
 | 
			
		||||
Scheduler process a drafted order + a first bill corresponding to every once buying. 
 | 
			
		||||
							
								
								
									
										144
									
								
								models/booking/booking.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								models/booking/booking.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
package booking
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
type Booking struct {
 | 
			
		||||
	utils.AbstractObject                       // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	PricedItem           pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error
 | 
			
		||||
 | 
			
		||||
	ResumeMetrics    map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"` 
 | 
			
		||||
	ExecutionMetrics map[string][]models.MetricsSnapshot       `json:"metrics,omitempty" bson:"metrics,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ExecutionsID      string             `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
 | 
			
		||||
	DestPeerID        string             `json:"dest_peer_id,omitempty"`                                                     // DestPeerID is the ID of the destination peer
 | 
			
		||||
	WorkflowID        string             `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"`                         // WorkflowID is the ID of the workflow
 | 
			
		||||
	ExecutionID       string             `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
 | 
			
		||||
	State             enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"`                             // State is the state of the booking
 | 
			
		||||
	ExpectedStartDate time.Time          `json:"expected_start_date,omitempty" bson:"expected_start_date,omitempty" validate:"required"` // ExpectedStartDate is the expected start date of the booking
 | 
			
		||||
	ExpectedEndDate   *time.Time         `json:"expected_end_date,omitempty" bson:"expected_end_date,omitempty" validate:"required"`     // ExpectedEndDate is the expected end date of the booking
 | 
			
		||||
 | 
			
		||||
	RealStartDate *time.Time `json:"real_start_date,omitempty" bson:"real_start_date,omitempty"` // RealStartDate is the real start date of the booking
 | 
			
		||||
	RealEndDate   *time.Time `json:"real_end_date,omitempty" bson:"real_end_date,omitempty"`     // RealEndDate is the real end date of the booking
 | 
			
		||||
 | 
			
		||||
	ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource
 | 
			
		||||
	ResourceID   string         `json:"resource_id,omitempty" bson:"resource_id,omitempty" validate:"required"`     // could be a Compute or a Storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResume {
 | 
			
		||||
	m := map[string]map[string]models.MetricResume{}
 | 
			
		||||
	for instance, snapshot := range b.ExecutionMetrics {
 | 
			
		||||
		m[instance] = map[string]models.MetricResume{}
 | 
			
		||||
		for _, metric := range snapshot {
 | 
			
		||||
			for _, mm := range metric.Metrics {
 | 
			
		||||
				if resume, ok := m[instance][mm.Name]; !ok {
 | 
			
		||||
					m[instance][mm.Name] = models.MetricResume{
 | 
			
		||||
						Delta:     0,
 | 
			
		||||
						LastValue: mm.Value,
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					delta := resume.LastValue - mm.Value
 | 
			
		||||
					if delta == 0 {
 | 
			
		||||
						resume.Delta = delta
 | 
			
		||||
					} else {
 | 
			
		||||
						resume.Delta = (resume.Delta + delta) / 2
 | 
			
		||||
					}
 | 
			
		||||
					resume.LastValue = mm.Value
 | 
			
		||||
					m[instance][mm.Name] = resume
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBooking checks if a booking is possible on a specific compute resource
 | 
			
		||||
func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) {
 | 
			
		||||
	// check if
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		// if no end... then Book like a savage
 | 
			
		||||
		e := start.Add(time.Hour)
 | 
			
		||||
		end = &e
 | 
			
		||||
	}
 | 
			
		||||
	accessor := NewAccessor(nil)
 | 
			
		||||
	res, code, err := accessor.Search(&dbs.Filters{
 | 
			
		||||
		And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date
 | 
			
		||||
			"resource_id": {{Operator: dbs.EQUAL.String(), Value: id}},
 | 
			
		||||
			"state":       {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
 | 
			
		||||
			"expected_start_date": {
 | 
			
		||||
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)},
 | 
			
		||||
				{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", wfa.IsDraft)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return len(res) <= parrallelAllowed, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetDelayForLaunch() time.Duration {
 | 
			
		||||
	return d.RealStartDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetDelayForFinishing() time.Duration {
 | 
			
		||||
	if d.ExpectedEndDate == nil {
 | 
			
		||||
		return time.Duration(0)
 | 
			
		||||
	}
 | 
			
		||||
	return d.RealEndDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetUsualDuration() time.Duration {
 | 
			
		||||
	return d.ExpectedEndDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetRealDuration() time.Duration {
 | 
			
		||||
	if d.RealEndDate == nil || d.RealStartDate == nil {
 | 
			
		||||
		return time.Duration(0)
 | 
			
		||||
	}
 | 
			
		||||
	return d.RealEndDate.Sub(*d.RealStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetDelayOnDuration() time.Duration {
 | 
			
		||||
	return d.GetRealDuration() - d.GetUsualDuration()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.State != set.(*Booking).State || r.RealStartDate != set.(*Booking).RealStartDate || r.RealEndDate != set.(*Booking).RealEndDate {
 | 
			
		||||
		return true, &Booking{
 | 
			
		||||
			State:         set.(*Booking).State,
 | 
			
		||||
			RealStartDate: set.(*Booking).RealStartDate,
 | 
			
		||||
			RealEndDate:   set.(*Booking).RealEndDate,
 | 
			
		||||
		} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	// TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										92
									
								
								models/booking/booking_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								models/booking/booking_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
package booking
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BookingMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the BookingMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *BookingMongoAccessor {
 | 
			
		||||
	return &BookingMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.BOOKING,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *BookingMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	if set.(*Booking).State == 0 {
 | 
			
		||||
		return nil, 400, errors.New("state is required")
 | 
			
		||||
	}
 | 
			
		||||
	realSet := &Booking{State: set.(*Booking).State}
 | 
			
		||||
	return utils.GenericUpdateOne(realSet, id, a, &Booking{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			return utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
		}
 | 
			
		||||
		if (d.(*Booking).ExpectedEndDate) == nil {
 | 
			
		||||
			d.(*Booking).State = enum.FORGOTTEN
 | 
			
		||||
			utils.GenericRawUpdateOne(d, id, a)
 | 
			
		||||
		} else if d.(*Booking).State == enum.SCHEDULED && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			d.(*Booking).State = enum.DELAYED
 | 
			
		||||
			utils.GenericRawUpdateOne(d, id, a)
 | 
			
		||||
		}
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Booking](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if d.(*Booking).State == enum.SCHEDULED && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			d.(*Booking).State = enum.DELAYED
 | 
			
		||||
			utils.GenericRawUpdateOne(d, d.GetID(), a)
 | 
			
		||||
		}
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								models/booking/tests/booking_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								models/booking/tests/booking_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
package booking_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBooking_GetDurations(t *testing.T) {
 | 
			
		||||
	start := time.Now().Add(-2 * time.Hour)
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	realStart := start.Add(30 * time.Minute)
 | 
			
		||||
	realEnd := realStart.Add(90 * time.Minute)
 | 
			
		||||
 | 
			
		||||
	b := &booking.Booking{
 | 
			
		||||
		ExpectedStartDate: start,
 | 
			
		||||
		ExpectedEndDate:   &end,
 | 
			
		||||
		RealStartDate:     &realStart,
 | 
			
		||||
		RealEndDate:       &realEnd,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, 30*time.Minute, b.GetDelayForLaunch())
 | 
			
		||||
	assert.Equal(t, 90*time.Minute, b.GetRealDuration())
 | 
			
		||||
	assert.Equal(t, end.Sub(start), b.GetUsualDuration())
 | 
			
		||||
	assert.Equal(t, b.GetRealDuration()-b.GetUsualDuration(), b.GetDelayOnDuration())
 | 
			
		||||
	assert.Equal(t, realEnd.Sub(start), b.GetDelayForFinishing())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	b := &booking.Booking{}
 | 
			
		||||
	accessor := b.GetAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.BOOKING, accessor.(*booking.BookingMongoAccessor).Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_VerifyAuth(t *testing.T) {
 | 
			
		||||
	assert.True(t, (&booking.Booking{}).VerifyAuth(nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	b := &booking.Booking{}
 | 
			
		||||
	b.StoreDraftDefault()
 | 
			
		||||
	assert.False(t, b.IsDraft)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_CanUpdate(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	b := &booking.Booking{
 | 
			
		||||
		State:          enum.SCHEDULED,
 | 
			
		||||
		AbstractObject: utils.AbstractObject{IsDraft: false},
 | 
			
		||||
		RealStartDate:  &now,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set := &booking.Booking{
 | 
			
		||||
		State:         enum.DELAYED,
 | 
			
		||||
		RealStartDate: &now,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ok, result := b.CanUpdate(set)
 | 
			
		||||
	assert.True(t, ok)
 | 
			
		||||
	assert.Equal(t, enum.DELAYED, result.(*booking.Booking).State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_CanDelete(t *testing.T) {
 | 
			
		||||
	b := &booking.Booking{AbstractObject: utils.AbstractObject{IsDraft: true}}
 | 
			
		||||
	assert.True(t, b.CanDelete())
 | 
			
		||||
 | 
			
		||||
	b.IsDraft = false
 | 
			
		||||
	assert.False(t, b.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	accessor := booking.NewAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.BOOKING, accessor.Type)
 | 
			
		||||
	assert.Equal(t, req, accessor.Request)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								models/collaborative_area/collaborative_area.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								models/collaborative_area/collaborative_area.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
package collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"slices"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CollaborativeAreaRule struct {
 | 
			
		||||
	ShareMode   string    `json:"share_mode,omitempty" bson:"share_mode,omitempty"`     // Share is the share of the rule
 | 
			
		||||
	CreatedAt   time.Time `json:"created_at,omitempty" bson:"created_at,omitempty"`     // CreatedAt is the time the rule was created
 | 
			
		||||
	Creator     string    `json:"creator,omitempty" bson:"creator,omitempty"`           // Creator is the creator of the rule
 | 
			
		||||
	ExploitedBy string    `json:"exploited_by,omitempty" bson:"exploited_by,omitempty"` // ExploitedBy is the exploited by of the rule
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SharedWorkspace is a struct that represents a shared workspace
 | 
			
		||||
// WARNING : it got a shallow object version, this one is the full version
 | 
			
		||||
// full version is the one used by API
 | 
			
		||||
// other one is a shallow version used by the Lib for import cycle problem purposes
 | 
			
		||||
type CollaborativeArea struct {
 | 
			
		||||
	utils.AbstractObject                         // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	IsSent                bool                   `json:"is_sent" bson:"-"`                                                       // IsSent is a flag that indicates if the workspace is sent
 | 
			
		||||
	Version               string                 `json:"version,omitempty" bson:"version,omitempty"`                             // Version is the version of the workspace
 | 
			
		||||
	Description           string                 `json:"description,omitempty" bson:"description,omitempty" validate:"required"` // Description is the description of the workspace
 | 
			
		||||
	CollaborativeAreaRule *CollaborativeAreaRule `json:"collaborative_area,omitempty" bson:"collaborative_area,omitempty"`       // CollaborativeArea is the collaborative area of the workspace
 | 
			
		||||
	Attributes            map[string]interface{} `json:"attributes,omitempty" bson:"attributes,omitempty"`                       // Attributes is the attributes of the workspace (TODO)
 | 
			
		||||
	Workspaces            []string               `json:"workspaces" bson:"workspaces"`                                           // Workspaces is the workspaces of the workspace
 | 
			
		||||
	Workflows             []string               `json:"workflows" bson:"workflows"`                                             // Workflows is the workflows of the workspace
 | 
			
		||||
	AllowedPeersGroup     map[string][]string    `json:"allowed_peers_group" bson:"allowed_peers_group"`                         // AllowedPeersGroup is the group of allowed peers
 | 
			
		||||
	Rules                 []string               `json:"rules" bson:"rules,omitempty"`                                           // Rules is the rules of the workspace
 | 
			
		||||
 | 
			
		||||
	SharedRules      []*rule.Rule           `json:"shared_rules,omitempty" bson:"-"`      // SharedRules is the shared rules of the workspace
 | 
			
		||||
	SharedWorkspaces []*workspace.Workspace `json:"shared_workspaces,omitempty" bson:"-"` // SharedWorkspaces is the shared workspaces of the workspace
 | 
			
		||||
	SharedWorkflows  []*w.Workflow          `json:"shared_workflows,omitempty" bson:"-"`  // SharedWorkflows is the shared workflows of the workspace
 | 
			
		||||
	SharedPeers      []*peer.Peer           `json:"shared_peers,omitempty" bson:"-"`      // SharedPeers is the shared peers of the workspace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *CollaborativeArea) Clear(peerID string) {
 | 
			
		||||
	if ao.AllowedPeersGroup == nil {
 | 
			
		||||
		ao.AllowedPeersGroup = map[string][]string{}
 | 
			
		||||
	}
 | 
			
		||||
	ao.CreatorID = peerID
 | 
			
		||||
	if config.GetConfig().Whitelist {
 | 
			
		||||
		ao.AllowedPeersGroup[peerID] = []string{"*"}
 | 
			
		||||
	} else {
 | 
			
		||||
		ao.AllowedPeersGroup[peerID] = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	// then reset the shared fields
 | 
			
		||||
	if ao.Workspaces == nil {
 | 
			
		||||
		ao.Workspaces = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.Workflows == nil {
 | 
			
		||||
		ao.Workflows = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.Rules == nil {
 | 
			
		||||
		ao.Rules = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.CollaborativeAreaRule == nil {
 | 
			
		||||
		ao.CollaborativeAreaRule = &CollaborativeAreaRule{
 | 
			
		||||
			ShareMode:   "private",
 | 
			
		||||
			ExploitedBy: "collaborators only",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil {
 | 
			
		||||
		if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist {
 | 
			
		||||
			if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
			for _, grp := range grps {
 | 
			
		||||
				if slices.Contains(request.Groups, grp) {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ao.AbstractObject.VerifyAuth(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) Trim() *CollaborativeArea {
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) StoreDraftDefault() {
 | 
			
		||||
	d.AllowedPeersGroup = map[string][]string{
 | 
			
		||||
		d.CreatorID: []string{"*"},
 | 
			
		||||
	}
 | 
			
		||||
	d.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								models/collaborative_area/collaborative_area_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								models/collaborative_area/collaborative_area_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
package collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SharedWorkspace is a struct that represents a collaborative area
 | 
			
		||||
type collaborativeAreaMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
 | 
			
		||||
	workspaceAccessor utils.Accessor
 | 
			
		||||
	workflowAccessor  utils.Accessor
 | 
			
		||||
	peerAccessor      utils.Accessor
 | 
			
		||||
	ruleAccessor      utils.Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor {
 | 
			
		||||
	return &collaborativeAreaMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.COLLABORATIVE_AREA,
 | 
			
		||||
		},
 | 
			
		||||
		workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
 | 
			
		||||
		workflowAccessor:  (&w.Workflow{}).GetAccessor(request),
 | 
			
		||||
		peerAccessor:      (&peer.Peer{}).GetAccessor(request),
 | 
			
		||||
		ruleAccessor:      (&rule.Rule{}).GetAccessor(request),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteOne deletes a collaborative area from the database, given its ID, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set, code, err := a.LoadOne(id)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	a.deleteToPeer(set.(*CollaborativeArea))
 | 
			
		||||
	a.sharedWorkflow(&CollaborativeArea{}, id)  // create all shared workflows
 | 
			
		||||
	a.sharedWorkspace(&CollaborativeArea{}, id) // create all collaborative areas
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)        // then add on yours
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateOne updates a collaborative area in the database, given its ID and the new data, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := utils.GenericUpdateOne(set.(*CollaborativeArea).Trim(), id, a, &CollaborativeArea{})
 | 
			
		||||
	// a.deleteToPeer(res.(*CollaborativeArea))        // delete the collaborative area on the peer
 | 
			
		||||
	a.sharedWorkflow(res.(*CollaborativeArea), id)  // replace all shared workflows
 | 
			
		||||
	a.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one)
 | 
			
		||||
	// a.sendToPeer(res.(*CollaborativeArea))  // send the collaborative area (collaborative area object) to the peers
 | 
			
		||||
	return res, code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	_, id := (&peer.Peer{}).IsMySelf()  // get the local peer
 | 
			
		||||
	data.(*CollaborativeArea).Clear(id) // set the creator
 | 
			
		||||
	// retrieve or proper peer
 | 
			
		||||
	if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
 | 
			
		||||
		data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{}
 | 
			
		||||
	}
 | 
			
		||||
	data.(*CollaborativeArea).CollaborativeAreaRule.Creator = id
 | 
			
		||||
	d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a)
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		a.sharedWorkflow(d.(*CollaborativeArea), d.GetID())  // create all shared workflows
 | 
			
		||||
		a.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas
 | 
			
		||||
		a.sendToPeer(d.(*CollaborativeArea))                 // send the collaborative area (collaborative area object) to the peers
 | 
			
		||||
	}
 | 
			
		||||
	return data, code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CopyOne copies a CollaborativeArea in the database
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return a.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T {
 | 
			
		||||
	var new []T
 | 
			
		||||
	res, code, _ := a.Search(&dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{
 | 
			
		||||
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", isDrafted)
 | 
			
		||||
	fmt.Println(res, arr, isDrafted, a)
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		for _, r := range res {
 | 
			
		||||
			new = append(new, r.(T))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return new
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// enrich is a function that enriches the CollaborativeArea with the shared objects
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea, isDrafted bool, request *tools.APIRequest) *CollaborativeArea {
 | 
			
		||||
	sharedWorkspace.SharedWorkspaces = filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, isDrafted, a.workspaceAccessor)
 | 
			
		||||
	sharedWorkspace.SharedWorkflows = filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, isDrafted, a.workflowAccessor)
 | 
			
		||||
	peerskey := []string{}
 | 
			
		||||
	fmt.Println("PEERS 1", sharedWorkspace.AllowedPeersGroup)
 | 
			
		||||
	for k, v := range sharedWorkspace.AllowedPeersGroup {
 | 
			
		||||
		canFound := false
 | 
			
		||||
		for _, t := range request.Groups {
 | 
			
		||||
			if slices.Contains(v, t) {
 | 
			
		||||
				canFound = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println("PEERS 2", canFound, v)
 | 
			
		||||
		if slices.Contains(v, "*") || canFound {
 | 
			
		||||
			peerskey = append(peerskey, k)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("PEERS", peerskey)
 | 
			
		||||
	sharedWorkspace.SharedPeers = filterEnrich[*peer.Peer](peerskey, isDrafted, a.peerAccessor)
 | 
			
		||||
	sharedWorkspace.SharedRules = filterEnrich[*rule.Rule](sharedWorkspace.Rules, isDrafted, a.ruleAccessor)
 | 
			
		||||
	return sharedWorkspace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return a.enrich(d.(*CollaborativeArea), false, a.Request), 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
 | 
			
		||||
	}, isDrafted, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
 | 
			
		||||
		}, isDrafted, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
*/
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeArea, id string) {
 | 
			
		||||
	eldest, code, _ := a.LoadOne(id) // get the eldest
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		eld := eldest.(*CollaborativeArea)
 | 
			
		||||
		if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string
 | 
			
		||||
			for _, v := range eld.Workspaces {
 | 
			
		||||
				a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v)
 | 
			
		||||
				if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				paccess := (&peer.Peer{})                 // send to all peers
 | 
			
		||||
				for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer
 | 
			
		||||
					b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.GetCaller())
 | 
			
		||||
					if err != nil && b == nil {
 | 
			
		||||
						a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if shared.Workspaces != nil {
 | 
			
		||||
		for _, v := range shared.Workspaces { // update all the collaborative areas
 | 
			
		||||
			workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace
 | 
			
		||||
			if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			for k := range shared.AllowedPeersGroup {
 | 
			
		||||
				if code != 200 {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer
 | 
			
		||||
				s := workspace.Serialize(workspace)
 | 
			
		||||
				s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
 | 
			
		||||
				b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.GetCaller())
 | 
			
		||||
				if err != nil && b == nil {
 | 
			
		||||
					a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// deleting on peers before adding, to avoid conflicts on peers side
 | 
			
		||||
	// because you have no reference to the remote collaborative area
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkflow is a function that shares the shared workflow to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeArea, id string) {
 | 
			
		||||
	eldest, code, _ := a.LoadOne(id) // get the eldest
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		eld := eldest.(*CollaborativeArea)
 | 
			
		||||
		if eld.Workflows != nil {
 | 
			
		||||
			for _, v := range eld.Workflows {
 | 
			
		||||
				data, code, _ := a.workflowAccessor.LoadOne(v)
 | 
			
		||||
				if code == 200 {
 | 
			
		||||
					s := data.(*w.Workflow)
 | 
			
		||||
					new := []string{}
 | 
			
		||||
					for _, id2 := range s.Shared {
 | 
			
		||||
						if id2 != id {
 | 
			
		||||
							new = append(new, id2)
 | 
			
		||||
						}
 | 
			
		||||
					} // kick the shared reference in your old shared workflow
 | 
			
		||||
					n := &w.Workflow{}
 | 
			
		||||
					n.Shared = new
 | 
			
		||||
					a.workflowAccessor.UpdateOne(n, v)
 | 
			
		||||
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					paccess := (&peer.Peer{})                 // send to all peers
 | 
			
		||||
					for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer
 | 
			
		||||
						b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.GetCaller())
 | 
			
		||||
						if err != nil && b == nil {
 | 
			
		||||
							a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if shared.Workflows != nil { // update all the shared workflows
 | 
			
		||||
		for _, v := range shared.Workflows {
 | 
			
		||||
			data, code, _ := a.workflowAccessor.LoadOne(v)
 | 
			
		||||
			if code == 200 {
 | 
			
		||||
				s := data.(*w.Workflow)
 | 
			
		||||
				if !slices.Contains(s.Shared, id) {
 | 
			
		||||
					s.Shared = append(s.Shared, id)
 | 
			
		||||
					workflow, code, _ := a.workflowAccessor.UpdateOne(s, v)
 | 
			
		||||
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					paccess := (&peer.Peer{})
 | 
			
		||||
					for k := range shared.AllowedPeersGroup { // send to all peers
 | 
			
		||||
						if code == 200 {
 | 
			
		||||
							s := workflow.Serialize(workflow) // add the shared workflow on the peer
 | 
			
		||||
							s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
 | 
			
		||||
							b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.GetCaller())
 | 
			
		||||
							if err != nil && b == nil {
 | 
			
		||||
								a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// deleting on peers before adding, to avoid conflicts on peers side
 | 
			
		||||
	// because you have no reference to the remote shared workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) {
 | 
			
		||||
	a.contactPeer(shared, tools.POST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) {
 | 
			
		||||
	a.contactPeer(shared, tools.POST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) {
 | 
			
		||||
	if a.GetCaller() == nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.COLLABORATIVE_AREA] == nil || a.GetCaller().Disabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	paccess := (&peer.Peer{})
 | 
			
		||||
	for k := range shared.AllowedPeersGroup {
 | 
			
		||||
		if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: k}}).IsMySelf(); ok || (shared.IsSent && meth == tools.POST) || (!shared.IsSent && meth != tools.POST) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		shared.IsSent = meth == tools.POST
 | 
			
		||||
		b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.GetCaller())
 | 
			
		||||
		if err != nil && b == nil {
 | 
			
		||||
			a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								models/collaborative_area/rules/rule/rule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								models/collaborative_area/rules/rule/rule.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
package rule
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Rule is a struct that represents a rule of a shared workspace
 | 
			
		||||
 */
 | 
			
		||||
type Rule struct {
 | 
			
		||||
	utils.AbstractObject          // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	Description          string   `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the rule
 | 
			
		||||
	Condition            string   `json:"condition,omitempty" bson:"condition,omitempty"`     // NOT DEFINITIVE TO SPECIFICATION
 | 
			
		||||
	Actions              []string `json:"actions,omitempty" bson:"actions,omitempty"`         // NOT DEFINITIVE TO SPECIFICATION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Rule) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Rule) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								models/collaborative_area/rules/rule/rule_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/collaborative_area/rules/rule/rule_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
package rule
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ruleMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the ruleMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor {
 | 
			
		||||
	return &ruleMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.RULE,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Rule{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Rule](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Rule](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Rule](filters, search, (&Rule{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package shallow_collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ShallowCollaborativeArea struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	IsSent      bool                   `json:"is_sent" bson:"-"`
 | 
			
		||||
	Version     string                 `json:"version,omitempty" bson:"version,omitempty"`
 | 
			
		||||
	Description string                 `json:"description,omitempty" bson:"description,omitempty" validate:"required"`
 | 
			
		||||
	Attributes  map[string]interface{} `json:"attributes,omitempty" bson:"attributes,omitempty"`
 | 
			
		||||
	Workspaces  []string               `json:"workspaces" bson:"workspaces"`
 | 
			
		||||
	Workflows   []string               `json:"workflows" bson:"workflows"`
 | 
			
		||||
	Peers       []string               `json:"peers" bson:"peers"`
 | 
			
		||||
	Rules       []string               `json:"rules,omitempty" bson:"rules,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ShallowCollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package shallow_collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type shallowSharedWorkspaceMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor {
 | 
			
		||||
	return &shallowSharedWorkspaceMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,                                              // Set the caller
 | 
			
		||||
			Type:    tools.COLLABORATIVE_AREA,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set.(*ShallowCollaborativeArea), id, a, &ShallowCollaborativeArea{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*ShallowCollaborativeArea), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return a.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*ShallowCollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								models/common/enum/infrastructure.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/common/enum/infrastructure.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type InfrastructureType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DOCKER InfrastructureType = iota
 | 
			
		||||
	KUBERNETES
 | 
			
		||||
	SLURM
 | 
			
		||||
	HW
 | 
			
		||||
	CONDOR
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t InfrastructureType) String() string {
 | 
			
		||||
	return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// get list of all infrastructure types
 | 
			
		||||
func InfrastructureList() []InfrastructureType {
 | 
			
		||||
	return []InfrastructureType{DOCKER, KUBERNETES, SLURM, HW, CONDOR}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								models/common/enum/size.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								models/common/enum/size.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type StorageSize int
 | 
			
		||||
 | 
			
		||||
// StorageType - Enum that defines the type of storage
 | 
			
		||||
const (
 | 
			
		||||
	GB StorageSize = iota
 | 
			
		||||
	MB
 | 
			
		||||
	KB
 | 
			
		||||
	TB
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var argoType = [...]string{
 | 
			
		||||
	"Gi",
 | 
			
		||||
	"Mi",
 | 
			
		||||
	"Ki",
 | 
			
		||||
	"Ti",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Size to string
 | 
			
		||||
func (t StorageSize) String() string {
 | 
			
		||||
	return [...]string{"GB", "MB", "KB", "TB"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SizeList() []StorageSize {
 | 
			
		||||
	return []StorageSize{GB, MB, KB, TB}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the StorageResource struct
 | 
			
		||||
func (dma StorageSize) ToArgo() string {
 | 
			
		||||
	return argoType[dma]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// enum of a data type
 | 
			
		||||
type StorageType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	FILE = iota
 | 
			
		||||
	STREAM
 | 
			
		||||
	API
 | 
			
		||||
	DATABASE
 | 
			
		||||
	S3
 | 
			
		||||
	MEMORY
 | 
			
		||||
	HARDWARE
 | 
			
		||||
	AZURE
 | 
			
		||||
	GCS
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// String() - Returns the string representation of the storage type
 | 
			
		||||
func (t StorageType) String() string {
 | 
			
		||||
	return [...]string{"FILE", "STREAM", "API", "DATABASE", "S3", "MEMORY", "HARDWARE", "AZURE", "GCS"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TypeList() []StorageType {
 | 
			
		||||
	return []StorageType{FILE, STREAM, API, DATABASE, S3, MEMORY, HARDWARE, AZURE, GCS}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								models/common/enum/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								models/common/enum/status.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type CompletionStatus int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DRAFTED CompletionStatus = iota
 | 
			
		||||
	PENDING
 | 
			
		||||
	CANCEL
 | 
			
		||||
	PARTIAL
 | 
			
		||||
	PAID
 | 
			
		||||
	DISPUTED
 | 
			
		||||
	OVERDUE
 | 
			
		||||
	REFUND
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (d CompletionStatus) String() string {
 | 
			
		||||
	return [...]string{"drafted", "pending", "cancel", "partial", "paid", "disputed", "overdue", "refund"}[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CompletionStatusList() []CompletionStatus {
 | 
			
		||||
	return []CompletionStatus{DRAFTED, PENDING, CANCEL, PARTIAL, PAID, DISPUTED, OVERDUE, REFUND}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BookingStatus int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DRAFT BookingStatus = iota
 | 
			
		||||
	SCHEDULED
 | 
			
		||||
	STARTED
 | 
			
		||||
	FAILURE
 | 
			
		||||
	SUCCESS
 | 
			
		||||
	FORGOTTEN
 | 
			
		||||
	DELAYED
 | 
			
		||||
	CANCELLED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var str = [...]string{
 | 
			
		||||
	"draft",
 | 
			
		||||
	"scheduled",
 | 
			
		||||
	"started",
 | 
			
		||||
	"failure",
 | 
			
		||||
	"success",
 | 
			
		||||
	"forgotten",
 | 
			
		||||
	"delayed",
 | 
			
		||||
	"cancelled",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FromInt(i int) string {
 | 
			
		||||
	return str[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d BookingStatus) String() string {
 | 
			
		||||
	return str[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnumIndex - Creating common behavior - give the type a EnumIndex functio
 | 
			
		||||
func (d BookingStatus) EnumIndex() int {
 | 
			
		||||
	return int(d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List
 | 
			
		||||
func StatusList() []BookingStatus {
 | 
			
		||||
	return []BookingStatus{DRAFT, SCHEDULED, STARTED, FAILURE, SUCCESS, FORGOTTEN, DELAYED, CANCELLED}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								models/common/models/access_configuration.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/common/models/access_configuration.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Container struct {
 | 
			
		||||
	Image   string            `json:"image,omitempty" bson:"image,omitempty"`     // Image is the container image TEMPO
 | 
			
		||||
	Command string            `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command
 | 
			
		||||
	Args    string            `json:"args,omitempty" bson:"args,omitempty"`       // Args is the container arguments
 | 
			
		||||
	Env     map[string]string `json:"env,omitempty" bson:"env,omitempty"`         // Env is the container environment variables
 | 
			
		||||
	Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes
 | 
			
		||||
 | 
			
		||||
	Exposes []Expose `bson:"exposes,omitempty" json:"exposes,omitempty"` // Expose is the execution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Expose struct {
 | 
			
		||||
	Port    int    `json:"port,omitempty" bson:"port,omitempty"`       // Port is the port
 | 
			
		||||
	Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse
 | 
			
		||||
	PAT     int    `json:"pat,omitempty" bson:"pat,omitempty"`         // PAT is the PAT
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								models/common/models/devices.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/common/models/devices.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// CPU is a struct that represents a CPU
 | 
			
		||||
type CPU struct {
 | 
			
		||||
	Model        string  `bson:"model,omitempty" json:"model,omitempty"`
 | 
			
		||||
	FrequencyGhz float64 `bson:"frequency,omitempty" json:"frequency,omitempty"`
 | 
			
		||||
	Cores        int     `bson:"cores,omitempty" json:"cores,omitempty"`
 | 
			
		||||
	Architecture string  `bson:"architecture,omitempty" json:"architecture,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RAM struct {
 | 
			
		||||
	SizeGb float64 `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"`
 | 
			
		||||
	Ecc    bool    `bson:"ecc" json:"ecc" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GPU struct {
 | 
			
		||||
	Model    string         `bson:"model,omitempty" json:"model,omitempty"`
 | 
			
		||||
	MemoryGb float64        `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"`
 | 
			
		||||
	Cores    map[string]int `bson:"cores,omitempty" json:"cores,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								models/common/models/inoutputs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								models/common/models/inoutputs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Artifact struct {
 | 
			
		||||
	AttrPath string `json:"attr_path,omitempty" bson:"attr_path,omitempty" validate:"required"`
 | 
			
		||||
	AttrFrom string `json:"from_path,omitempty" bson:"from_path,omitempty"`
 | 
			
		||||
	Readonly bool   `json:"readonly" bson:"readonly" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Param struct {
 | 
			
		||||
	Name      string `json:"name" bson:"name" validate:"required"`
 | 
			
		||||
	Attr      string `json:"attr,omitempty" bson:"attr,omitempty"`
 | 
			
		||||
	Value     string `json:"value,omitempty" bson:"value,omitempty"`
 | 
			
		||||
	Origin    string `json:"origin,omitempty" bson:"origin,omitempty"`
 | 
			
		||||
	Readonly  bool   `json:"readonly" bson:"readonly" default:"true"`
 | 
			
		||||
	Optionnal bool   `json:"optionnal" bson:"optionnal" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InOutputs struct {
 | 
			
		||||
	Params    []Param    `json:"parameters" bson:"parameters"`
 | 
			
		||||
	Artifacts []Artifact `json:"artifacts" bson:"artifacts"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								models/common/models/metric.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/common/models/metric.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type MetricsSnapshot struct {
 | 
			
		||||
	From    string   `json:"origin"`
 | 
			
		||||
	Metrics []Metric `json:"metrics"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metric struct {
 | 
			
		||||
	Name  string  `json:"name"`
 | 
			
		||||
	Value float64 `json:"value"`
 | 
			
		||||
	Error error   `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricResume struct {
 | 
			
		||||
	Delta     float64 `json:"delta"`
 | 
			
		||||
	LastValue float64 `json:"last_value"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								models/common/planner.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								models/common/planner.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetPlannerNearestStart(start time.Time, planned map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest) float64 {
 | 
			
		||||
	near := float64(10000000000)    // set a high value
 | 
			
		||||
	for _, items := range planned { // loop through the planned items
 | 
			
		||||
		for _, priced := range items { // loop through the priced items
 | 
			
		||||
			if priced.GetLocationStart() == nil { // if the start is nil,
 | 
			
		||||
				continue // skip the iteration
 | 
			
		||||
			}
 | 
			
		||||
			newS := priced.GetLocationStart()     // get the start
 | 
			
		||||
			if newS.Sub(start).Seconds() < near { // if the difference between the start and the new start is less than the nearest start
 | 
			
		||||
				near = newS.Sub(start).Seconds()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return near
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetPlannerLongestTime(end *time.Time, planned map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest) float64 {
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		return -1
 | 
			
		||||
	}
 | 
			
		||||
	longestTime := float64(0)
 | 
			
		||||
	for _, priced := range planned[tools.PROCESSING_RESOURCE] {
 | 
			
		||||
		if priced.GetLocationEnd() == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		newS := priced.GetLocationEnd()
 | 
			
		||||
		if end == nil && longestTime < newS.Sub(*end).Seconds() {
 | 
			
		||||
			longestTime = newS.Sub(*end).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		// get the nearest start from start var
 | 
			
		||||
	}
 | 
			
		||||
	return longestTime
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								models/common/pricing/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								models/common/pricing/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricedItemITF interface {
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetType() tools.DataType
 | 
			
		||||
	IsPurchasable() bool
 | 
			
		||||
	IsBooked() bool
 | 
			
		||||
	GetCreatorID() string
 | 
			
		||||
	SelectPricing() PricingProfileITF
 | 
			
		||||
	GetLocationStart() *time.Time
 | 
			
		||||
	SetLocationStart(start time.Time)
 | 
			
		||||
	SetLocationEnd(end time.Time)
 | 
			
		||||
	GetLocationEnd() *time.Time
 | 
			
		||||
	GetExplicitDurationInS() float64
 | 
			
		||||
	GetPrice() (float64, error)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								models/common/pricing/pricing_profile.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										66
									
								
								models/common/pricing/pricing_profile.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricingProfileITF interface {
 | 
			
		||||
	IsBooked() bool
 | 
			
		||||
	IsPurchasable() bool
 | 
			
		||||
	GetPurchase() BuyingStrategy
 | 
			
		||||
	GetOverrideStrategyValue() int
 | 
			
		||||
	GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RefundType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	REFUND_DEAD_END RefundType = iota
 | 
			
		||||
	REFUND_ON_ERROR
 | 
			
		||||
	REFUND_ON_EARLY_END
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t RefundType) String() string {
 | 
			
		||||
	return [...]string{"REFUND ON DEAD END", "REFUND ON ERROR", "REFUND ON EARLY END"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RefundTypeList() []RefundType {
 | 
			
		||||
	return []RefundType{REFUND_DEAD_END, REFUND_ON_ERROR, REFUND_ON_EARLY_END}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessPricingProfile[T Strategy] struct { // only use for acces such as : DATA && PROCESSING
 | 
			
		||||
	Pricing       PricingStrategy[T] `json:"pricing,omitempty" bson:"pricing,omitempty"`   // Price is the price of the resource
 | 
			
		||||
	DefaultRefund RefundType         `json:"default_refund" bson:"default_refund"`         // DefaultRefund is the default refund type of the pricing
 | 
			
		||||
	RefundRatio   int32              `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int {
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitPrivilegeStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	BASIC ExploitPrivilegeStrategy = iota
 | 
			
		||||
	GARANTED_ON_DELAY
 | 
			
		||||
	GARANTED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ExploitPrivilegeStrategyList() []ExploitPrivilegeStrategy {
 | 
			
		||||
	return []ExploitPrivilegeStrategy{BASIC, GARANTED_ON_DELAY, GARANTED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t ExploitPrivilegeStrategy) String() string {
 | 
			
		||||
	return [...]string{"NO GARANTY", "GARANTED ON SPECIFIC DELAY", "GARANTED"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitPricingProfile[T Strategy] struct { // only use for exploit such as : STORAGE, COMPUTE, WORKFLOW
 | 
			
		||||
	AccessPricingProfile[T]
 | 
			
		||||
	AdditionnalRefundTypes []RefundType `json:"refund_types" bson:"refund_types"` // RefundTypes is the refund types of the pricing
 | 
			
		||||
 | 
			
		||||
	PrivilegeStrategy   ExploitPrivilegeStrategy `json:"privilege_strategy,omitempty" bson:"privilege_strategy,omitempty"`       // Strategy is the strategy of the pricing
 | 
			
		||||
	GarantedDelaySecond uint                     `json:"garanted_delay_second,omitempty" bson:"garanted_delay_second,omitempty"` // GarantedDelaySecond is the garanted delay of the pricing
 | 
			
		||||
 | 
			
		||||
	Exceeding      bool  `json:"exceeding" bson:"exceeding"`                         // Exceeding is the exceeding of the bill
 | 
			
		||||
	ExceedingRatio int32 `json:"exceeding_ratio" bson:"exceeding_ratio" default:"0"` // ExceedingRatio is the exceeding ratio of the bill
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										185
									
								
								models/common/pricing/pricing_strategy.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										185
									
								
								models/common/pricing/pricing_strategy.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BillingStrategy int // BAM BAM
 | 
			
		||||
 | 
			
		||||
// should except... on
 | 
			
		||||
const (
 | 
			
		||||
	BILL_ONCE BillingStrategy = iota // is a permanent buying ( predictible )
 | 
			
		||||
	BILL_PER_WEEK
 | 
			
		||||
	BILL_PER_MONTH
 | 
			
		||||
	BILL_PER_YEAR
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t BillingStrategy) IsBillingStrategyAllowed(bs int) (BillingStrategy, bool) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case BILL_ONCE:
 | 
			
		||||
		return BILL_ONCE, bs == 0
 | 
			
		||||
	case BILL_PER_WEEK:
 | 
			
		||||
	case BILL_PER_MONTH:
 | 
			
		||||
	case BILL_PER_YEAR:
 | 
			
		||||
		return t, bs != 0
 | 
			
		||||
	}
 | 
			
		||||
	return t, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t BillingStrategy) String() string {
 | 
			
		||||
	return [...]string{"BILL_ONCE", "BILL_PER_WEEK", "BILL_PER_MONTH", "BILL_PER_YEAR"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BillingStrategyList() []BillingStrategy {
 | 
			
		||||
	return []BillingStrategy{BILL_ONCE, BILL_PER_WEEK, BILL_PER_MONTH, BILL_PER_YEAR}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BuyingStrategy int
 | 
			
		||||
 | 
			
		||||
// should except... on
 | 
			
		||||
const (
 | 
			
		||||
	PERMANENT              BuyingStrategy = iota // is a permanent buying ( predictible )
 | 
			
		||||
	UNDEFINED_SUBSCRIPTION                       // a endless subscription ( unpredictible )
 | 
			
		||||
	SUBSCRIPTION                                 // a defined subscription ( predictible )
 | 
			
		||||
	// PAY_PER_USE                                  // per request. ( unpredictible )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t BuyingStrategy) String() string {
 | 
			
		||||
	return [...]string{"PERMANENT", "UNDEFINED_SUBSCRIPTION", "SUBSCRIPTION"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t BuyingStrategy) IsBillingStrategyAllowed(bs BillingStrategy) (BillingStrategy, bool) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PERMANENT:
 | 
			
		||||
		return BILL_ONCE, bs == BILL_ONCE
 | 
			
		||||
	case UNDEFINED_SUBSCRIPTION:
 | 
			
		||||
		return BILL_PER_MONTH, bs != BILL_ONCE
 | 
			
		||||
	case SUBSCRIPTION:
 | 
			
		||||
		/*case PAY_PER_USE:
 | 
			
		||||
		return bs, true*/
 | 
			
		||||
	}
 | 
			
		||||
	return bs, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BuyingStrategyList() []BuyingStrategy {
 | 
			
		||||
	return []BuyingStrategy{PERMANENT, UNDEFINED_SUBSCRIPTION, SUBSCRIPTION}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Strategy interface {
 | 
			
		||||
	GetStrategy() string
 | 
			
		||||
	GetStrategyValue() int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TimePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ONCE TimePricingStrategy = iota
 | 
			
		||||
	PER_SECOND
 | 
			
		||||
	PER_MINUTE
 | 
			
		||||
	PER_HOUR
 | 
			
		||||
	PER_DAY
 | 
			
		||||
	PER_WEEK
 | 
			
		||||
	PER_MONTH
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func IsTimeStrategy(i int) bool {
 | 
			
		||||
	return len(TimePricingStrategyList()) < i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) String() string {
 | 
			
		||||
	return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TimePricingStrategyListStr() []string {
 | 
			
		||||
	return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TimePricingStrategyList() []TimePricingStrategy {
 | 
			
		||||
	return []TimePricingStrategy{ONCE, PER_SECOND, PER_MINUTE, PER_HOUR, PER_DAY, PER_WEEK, PER_MONTH}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) GetStrategy() string {
 | 
			
		||||
	return [...]string{"ONCE", "PER_SECOND", "PER_MINUTE", "PER_HOUR", "PER_DAY", "PER_WEEK", "PER_MONTH"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAverageTimeInSecond(averageTimeInSecond float64, start time.Time, end *time.Time) float64 {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	after := now.Add(time.Duration(averageTimeInSecond) * time.Second)
 | 
			
		||||
 | 
			
		||||
	fromAverageDuration := after.Sub(now).Seconds()
 | 
			
		||||
	var tEnd time.Time
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		tEnd = start.Add(1 * time.Hour)
 | 
			
		||||
	} else {
 | 
			
		||||
		tEnd = *end
 | 
			
		||||
	}
 | 
			
		||||
	fromDateDuration := tEnd.Sub(start).Seconds()
 | 
			
		||||
 | 
			
		||||
	if fromAverageDuration > fromDateDuration {
 | 
			
		||||
		return fromAverageDuration
 | 
			
		||||
	}
 | 
			
		||||
	return fromDateDuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BookingEstimation(t TimePricingStrategy, price float64, locationDurationInSecond float64, start time.Time, end *time.Time) (float64, error) {
 | 
			
		||||
	locationDurationInSecond = getAverageTimeInSecond(locationDurationInSecond, start, end)
 | 
			
		||||
	priceStr := fmt.Sprintf("%v", price)
 | 
			
		||||
	p, err := strconv.ParseFloat(priceStr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	switch t {
 | 
			
		||||
	case ONCE:
 | 
			
		||||
		return p, nil
 | 
			
		||||
	case PER_HOUR:
 | 
			
		||||
		return p * float64(locationDurationInSecond/3600), nil
 | 
			
		||||
	case PER_MINUTE:
 | 
			
		||||
		return p * float64(locationDurationInSecond/60), nil
 | 
			
		||||
	case PER_SECOND:
 | 
			
		||||
		return p * locationDurationInSecond, nil
 | 
			
		||||
	case PER_DAY:
 | 
			
		||||
		return p * float64(locationDurationInSecond/86400), nil
 | 
			
		||||
	case PER_WEEK:
 | 
			
		||||
		return p * float64(locationDurationInSecond/604800), nil
 | 
			
		||||
	case PER_MONTH:
 | 
			
		||||
		return p * float64(locationDurationInSecond/2592000), nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// may suppress in pricing strategy -> to set in map
 | 
			
		||||
type PricingStrategy[T Strategy] struct {
 | 
			
		||||
	Price               float64             `json:"price" bson:"price" default:"0"`                                 // Price is the Price of the pricing
 | 
			
		||||
	Currency            string              `json:"currency" bson:"currency" default:"USD"`                         // Currency is the currency of the pricing
 | 
			
		||||
	BuyingStrategy      BuyingStrategy      `json:"buying_strategy" bson:"buying_strategy" default:"0"`             // BuyingStrategy is the buying strategy of the pricing
 | 
			
		||||
	TimePricingStrategy TimePricingStrategy `json:"time_pricing_strategy" bson:"time_pricing_strategy" default:"0"` // TimePricingStrategy is the time pricing strategy of the pricing
 | 
			
		||||
	OverrideStrategy    T                   `json:"override_strategy" bson:"override_strategy" default:"-1"`        // Modulation is the modulation of the pricing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) {
 | 
			
		||||
	if p.BuyingStrategy == SUBSCRIPTION {
 | 
			
		||||
		return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
 | 
			
		||||
	} else if p.BuyingStrategy == PERMANENT {
 | 
			
		||||
		return p.Price, nil
 | 
			
		||||
	}
 | 
			
		||||
	return p.Price * float64(amountOfData), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetBuyingStrategy() BuyingStrategy {
 | 
			
		||||
	return p.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
 | 
			
		||||
	return p.TimePricingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetOverrideStrategy() T {
 | 
			
		||||
	return p.OverrideStrategy
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								models/common/pricing/tests/pricing_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								models/common/pricing/tests/pricing_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
package pricing_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DummyStrategy int
 | 
			
		||||
 | 
			
		||||
func (d DummyStrategy) GetStrategy() string   { return "DUMMY" }
 | 
			
		||||
func (d DummyStrategy) GetStrategyValue() int { return int(d) }
 | 
			
		||||
 | 
			
		||||
func TestBuyingStrategy_String(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "UNLIMITED", pricing.PERMANENT.String())
 | 
			
		||||
	assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String())
 | 
			
		||||
	//assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuyingStrategyList(t *testing.T) {
 | 
			
		||||
	list := pricing.BuyingStrategyList()
 | 
			
		||||
	assert.Equal(t, 3, len(list))
 | 
			
		||||
	assert.Contains(t, list, pricing.SUBSCRIPTION)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategy_String(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "ONCE", pricing.ONCE.String())
 | 
			
		||||
	assert.Equal(t, "PER SECOND", pricing.PER_SECOND.String())
 | 
			
		||||
	assert.Equal(t, "PER MONTH", pricing.PER_MONTH.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategyList(t *testing.T) {
 | 
			
		||||
	list := pricing.TimePricingStrategyList()
 | 
			
		||||
	assert.Equal(t, 7, len(list))
 | 
			
		||||
	assert.Contains(t, list, pricing.PER_DAY)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategy_Methods(t *testing.T) {
 | 
			
		||||
	ts := pricing.PER_MINUTE
 | 
			
		||||
	assert.Equal(t, "PER_MINUTE", ts.GetStrategy())
 | 
			
		||||
	assert.Equal(t, 2, ts.GetStrategyValue())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_getAverageTimeInSecond_WithEnd(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(30 * time.Minute)
 | 
			
		||||
 | 
			
		||||
	_, err := pricing.BookingEstimation(pricing.PER_MINUTE, 2.0, 1200, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_getAverageTimeInSecond_WithoutEnd(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// getAverageTimeInSecond is tested via BookingEstimation
 | 
			
		||||
	price, err := pricing.BookingEstimation(pricing.PER_HOUR, 10.0, 100, start, nil)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, price > 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBookingEstimation(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
	strategies := map[pricing.TimePricingStrategy]float64{
 | 
			
		||||
		pricing.ONCE:       50,
 | 
			
		||||
		pricing.PER_HOUR:   10,
 | 
			
		||||
		pricing.PER_MINUTE: 1,
 | 
			
		||||
		pricing.PER_SECOND: 0.1,
 | 
			
		||||
		pricing.PER_DAY:    100,
 | 
			
		||||
		pricing.PER_WEEK:   500,
 | 
			
		||||
		pricing.PER_MONTH:  2000,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for strategy, price := range strategies {
 | 
			
		||||
		t.Run(strategy.String(), func(t *testing.T) {
 | 
			
		||||
			cost, err := pricing.BookingEstimation(strategy, price, 3600, start, &end)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.True(t, cost >= 0)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := pricing.BookingEstimation(999, 10, 3600, start, &end)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricingStrategy_Getters(t *testing.T) {
 | 
			
		||||
	ps := pricing.PricingStrategy[DummyStrategy]{
 | 
			
		||||
		Price:               20,
 | 
			
		||||
		Currency:            "USD",
 | 
			
		||||
		BuyingStrategy:      pricing.SUBSCRIPTION,
 | 
			
		||||
		TimePricingStrategy: pricing.PER_MINUTE,
 | 
			
		||||
		OverrideStrategy:    DummyStrategy(1),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, pricing.SUBSCRIPTION, ps.GetBuyingStrategy())
 | 
			
		||||
	assert.Equal(t, pricing.PER_MINUTE, ps.GetTimePricingStrategy())
 | 
			
		||||
	assert.Equal(t, DummyStrategy(1), ps.GetOverrideStrategy())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricingStrategy_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	// SUBSCRIPTION case
 | 
			
		||||
	ps := pricing.PricingStrategy[DummyStrategy]{
 | 
			
		||||
		Price:               5,
 | 
			
		||||
		BuyingStrategy:      pricing.SUBSCRIPTION,
 | 
			
		||||
		TimePricingStrategy: pricing.PER_HOUR,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p, err := ps.GetPrice(2, 3600, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, p > 0)
 | 
			
		||||
 | 
			
		||||
	// UNLIMITED case
 | 
			
		||||
	ps.BuyingStrategy = pricing.PERMANENT
 | 
			
		||||
	p, err = ps.GetPrice(10, 0, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 5.0, p)
 | 
			
		||||
 | 
			
		||||
	// PAY_PER_USE case
 | 
			
		||||
	//ps.BuyingStrategy = pricing.PAY_PER_USE
 | 
			
		||||
	p, err = ps.GetPrice(3, 0, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 15.0, p)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								models/live/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								models/live/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LiveInterface interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	GetMonitorPath() string
 | 
			
		||||
	GetResourcesID() []string
 | 
			
		||||
	SetResourcesID(string)
 | 
			
		||||
	GetResourceAccessor(request *tools.APIRequest) utils.Accessor
 | 
			
		||||
	GetResource() resources.ResourceInterface
 | 
			
		||||
	GetResourceInstance() resources.ResourceInstanceITF
 | 
			
		||||
	SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								models/live/live.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								models/live/live.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/biter777/countries"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveDatacenter is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
type Credentials struct {
 | 
			
		||||
	Login string `json:"login,omitempty" bson:"login,omitempty"`
 | 
			
		||||
	Pass  string `json:"password,omitempty" bson:"password,omitempty"`
 | 
			
		||||
	Token string `json:"token,omitempty" bson:"token,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Certs struct {
 | 
			
		||||
	AuthorityCertificate string `json:"authority_certificate,omitempty" bson:"authority_certificate,omitempty"`
 | 
			
		||||
	ClientCertificate    string `json:"client_certificate,omitempty" bson:"client_certificate,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LiveCerts struct {
 | 
			
		||||
	Host string `json:"host,omitempty" bson:"host,omitempty"`
 | 
			
		||||
	Port string `json:"port,omitempty" bson:"port,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Certificates *Certs       `json:"certs,omitempty" bson:"certs,omitempty"`
 | 
			
		||||
	Credentials  *Credentials `json:"creds,omitempty" bson:"creds,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO in the future multiple type of certs depending of infra type
 | 
			
		||||
 | 
			
		||||
type AbstractLive struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Certs LiveCerts `json:"certs,omitempty" bson:"certs,omitempty"`
 | 
			
		||||
 | 
			
		||||
	MonitorPath    string                `json:"monitor_path,omitempty" bson:"monitor_path,omitempty"`
 | 
			
		||||
	Location       resources.GeoPoint    `json:"location,omitempty" bson:"location,omitempty"`
 | 
			
		||||
	Country        countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
 | 
			
		||||
	AccessProtocol string                `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
 | 
			
		||||
	ResourcesID    []string              `json:"resources_id" bson:"resources_id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) GetMonitorPath() string {
 | 
			
		||||
	return d.MonitorPath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) GetResourcesID() []string {
 | 
			
		||||
	return d.ResourcesID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) SetResourcesID(id string) {
 | 
			
		||||
	if !slices.Contains(d.ResourcesID, id) {
 | 
			
		||||
		d.ResourcesID = append(d.ResourcesID, id)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) GetResourceType() tools.DataType {
 | 
			
		||||
	return tools.INVALID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft ComputeUnits can be deleted
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								models/live/live_datacenter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								models/live/live_datacenter.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveDatacenter is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type LiveDatacenter struct {
 | 
			
		||||
	AbstractLive
 | 
			
		||||
 | 
			
		||||
	StorageType enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // Type is the type of the storage
 | 
			
		||||
	Acronym     string           `bson:"acronym,omitempty" json:"acronym,omitempty"`    // Acronym is the acronym of the storage
 | 
			
		||||
 | 
			
		||||
	Architecture       string                   `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
 | 
			
		||||
	Infrastructure     enum.InfrastructureType  `json:"infrastructure" bson:"infrastructure" default:"-1"`    // Infrastructure is the infrastructure
 | 
			
		||||
	Source             string                   `json:"source,omitempty" bson:"source,omitempty"`             // Source is the source of the resource
 | 
			
		||||
	SecurityLevel      string                   `json:"security_level,omitempty" bson:"security_level,omitempty"`
 | 
			
		||||
	PowerSources       []string                 `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
 | 
			
		||||
	AnnualCO2Emissions float64                  `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
 | 
			
		||||
	CPUs               map[string]*models.CPU   `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs               map[string]*models.GPU   `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	Nodes              []*resources.ComputeNode `json:"nodes,omitempty" bson:"nodes,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*LiveDatacenter](tools.LIVE_DATACENTER, request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveDatacenter) GetResourceAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return resources.NewAccessor[*resources.ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &resources.ComputeResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) GetResource() resources.ResourceInterface {
 | 
			
		||||
	return &resources.ComputeResource{}
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveDatacenter) GetResourceInstance() resources.ResourceInstanceITF {
 | 
			
		||||
	return &resources.ComputeResourceInstance{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface {
 | 
			
		||||
	r := res.(*resources.ComputeResource)
 | 
			
		||||
	r.Instances = append(r.Instances, i.(*resources.ComputeResourceInstance))
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								models/live/live_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								models/live/live_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type computeUnitsMongoAccessor[T LiveInterface] struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the computeUnitsMongoAccessor
 | 
			
		||||
func NewAccessor[T LiveInterface](t tools.DataType, request *tools.APIRequest) *computeUnitsMongoAccessor[T] {
 | 
			
		||||
	return &computeUnitsMongoAccessor[T]{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// should verify if a source is existing...
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &LiveDatacenter{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*LiveDatacenter), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	// is a publisher... that become a resources.
 | 
			
		||||
	if data.IsDrafted() {
 | 
			
		||||
		return nil, 422, errors.New("can't publish a drafted compute units")
 | 
			
		||||
	}
 | 
			
		||||
	live := data.(T)
 | 
			
		||||
	if live.GetMonitorPath() == "" || live.GetID() != "" {
 | 
			
		||||
		return nil, 422, errors.New("publishing is only allowed is it can be monitored and be accessible")
 | 
			
		||||
	}
 | 
			
		||||
	if res, code, err := a.LoadOne(live.GetID()); err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	} else {
 | 
			
		||||
		live = res.(T)
 | 
			
		||||
	}
 | 
			
		||||
	resAccess := live.GetResourceAccessor(a.Request)
 | 
			
		||||
	instance := live.GetResourceInstance()
 | 
			
		||||
	b, _ := json.Marshal(live)
 | 
			
		||||
	json.Unmarshal(b, instance)
 | 
			
		||||
 | 
			
		||||
	if len(live.GetResourcesID()) > 0 {
 | 
			
		||||
		for _, r := range live.GetResourcesID() {
 | 
			
		||||
			// TODO dependent of a existing resource
 | 
			
		||||
			res, code, err := resAccess.LoadOne(r)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				return nil, code, err
 | 
			
		||||
			}
 | 
			
		||||
			existingResource := live.GetResource()
 | 
			
		||||
			b, _ := json.Marshal(res)
 | 
			
		||||
			json.Unmarshal(b, existingResource)
 | 
			
		||||
			live.SetResourceInstance(existingResource, instance)
 | 
			
		||||
			resAccess.UpdateOne(existingResource, existingResource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		if live.GetID() != "" {
 | 
			
		||||
			return a.LoadOne(live.GetID())
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.StoreOne(live)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		r := live.GetResource()
 | 
			
		||||
		b, _ := json.Marshal(live)
 | 
			
		||||
		json.Unmarshal(b, &r)
 | 
			
		||||
		live.SetResourceInstance(r, instance)
 | 
			
		||||
		res, code, err := resAccess.StoreOne(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, code, err
 | 
			
		||||
		}
 | 
			
		||||
		live.SetResourcesID(res.GetID())
 | 
			
		||||
		if live.GetID() != "" {
 | 
			
		||||
			return a.UpdateOne(live, live.GetID())
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.StoreOne(live)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[T](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*LiveDatacenter](filters, search, (&LiveDatacenter{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								models/live/live_storage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								models/live/live_storage.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveStorage is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type LiveStorage struct {
 | 
			
		||||
	AbstractLive
 | 
			
		||||
 | 
			
		||||
	Source        string           `bson:"source,omitempty" json:"source,omitempty"` // Source is the source of the storage
 | 
			
		||||
	Path          string           `bson:"path,omitempty" json:"path,omitempty"`     // Path is the store folders in the source
 | 
			
		||||
	Local         bool             `bson:"local" json:"local"`
 | 
			
		||||
	SecurityLevel string           `bson:"security_level,omitempty" json:"security_level,omitempty"`
 | 
			
		||||
	SizeType      enum.StorageSize `bson:"size_type" json:"size_type" default:"0"`           // SizeType is the type of the storage size
 | 
			
		||||
	SizeGB        int64            `bson:"size,omitempty" json:"size,omitempty"`             // Size is the size of the storage
 | 
			
		||||
	Encryption    bool             `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
 | 
			
		||||
	Redundancy    string           `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
 | 
			
		||||
	Throughput    string           `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*LiveStorage](tools.LIVE_STORAGE, request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveStorage) GetResourceAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return resources.NewAccessor[*resources.ComputeResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &resources.StorageResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) GetResource() resources.ResourceInterface {
 | 
			
		||||
	return &resources.StorageResource{}
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveStorage) GetResourceInstance() resources.ResourceInstanceITF {
 | 
			
		||||
	return &resources.StorageResourceInstance{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface {
 | 
			
		||||
	r := res.(*resources.StorageResource)
 | 
			
		||||
	r.Instances = append(r.Instances, i.(*resources.StorageResourceInstance))
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								models/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								models/models.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/bill"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/live"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	resource "cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	w3 "cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
This package contains the models used in the application
 | 
			
		||||
It's used to create the models dynamically
 | 
			
		||||
*/
 | 
			
		||||
var ModelsCatalog = map[string]func() utils.DBObject{
 | 
			
		||||
	tools.WORKFLOW_RESOURCE.String():   func() utils.DBObject { return &resource.WorkflowResource{} },
 | 
			
		||||
	tools.DATA_RESOURCE.String():       func() utils.DBObject { return &resource.DataResource{} },
 | 
			
		||||
	tools.COMPUTE_RESOURCE.String():    func() utils.DBObject { return &resource.ComputeResource{} },
 | 
			
		||||
	tools.STORAGE_RESOURCE.String():    func() utils.DBObject { return &resource.StorageResource{} },
 | 
			
		||||
	tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} },
 | 
			
		||||
	tools.WORKFLOW.String():            func() utils.DBObject { return &w2.Workflow{} },
 | 
			
		||||
	tools.WORKFLOW_EXECUTION.String():  func() utils.DBObject { return &workflow_execution.WorkflowExecution{} },
 | 
			
		||||
	tools.WORKSPACE.String():           func() utils.DBObject { return &w3.Workspace{} },
 | 
			
		||||
	tools.PEER.String():                func() utils.DBObject { return &peer.Peer{} },
 | 
			
		||||
	tools.COLLABORATIVE_AREA.String():  func() utils.DBObject { return &collaborative_area.CollaborativeArea{} },
 | 
			
		||||
	tools.RULE.String():                func() utils.DBObject { return &rule.Rule{} },
 | 
			
		||||
	tools.BOOKING.String():             func() utils.DBObject { return &booking.Booking{} },
 | 
			
		||||
	tools.WORKFLOW_HISTORY.String():    func() utils.DBObject { return &w2.WorkflowHistory{} },
 | 
			
		||||
	tools.WORKSPACE_HISTORY.String():   func() utils.DBObject { return &w3.WorkspaceHistory{} },
 | 
			
		||||
	tools.ORDER.String():               func() utils.DBObject { return &order.Order{} },
 | 
			
		||||
	tools.PURCHASE_RESOURCE.String():   func() utils.DBObject { return &purchase_resource.PurchaseResource{} },
 | 
			
		||||
	tools.LIVE_DATACENTER.String():     func() utils.DBObject { return &live.LiveDatacenter{} },
 | 
			
		||||
	tools.LIVE_STORAGE.String():        func() utils.DBObject { return &live.LiveStorage{} },
 | 
			
		||||
	tools.BILL.String():                func() utils.DBObject { return &bill.Bill{} },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Model returns the model object based on the model type
 | 
			
		||||
func Model(model int) utils.DBObject {
 | 
			
		||||
	log := logs.GetLogger()
 | 
			
		||||
	if _, ok := ModelsCatalog[tools.FromInt(model)]; ok {
 | 
			
		||||
		return ModelsCatalog[tools.FromInt(model)]()
 | 
			
		||||
	}
 | 
			
		||||
	log.Error().Msg("Can't find model " + tools.FromInt(model) + ".")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetModelsNames returns the names of the models
 | 
			
		||||
func GetModelsNames() []string {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for name := range ModelsCatalog {
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	return names
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								models/order/order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								models/order/order.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
package order
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type Order struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	ExecutionsID string                                `json:"executions_id" bson:"executions_id" validate:"required"`
 | 
			
		||||
	Status       enum.CompletionStatus                 `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	Purchases    []*purchase_resource.PurchaseResource `json:"purchases" bson:"purchases"`
 | 
			
		||||
	Bookings     []*booking.Booking                    `json:"bookings" bson:"bookings"`
 | 
			
		||||
 | 
			
		||||
	Billing map[pricing.BillingStrategy][]*booking.Booking `json:"billing" bson:"billing"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.Status != set.(*Order).Status {
 | 
			
		||||
		return true, &Order{Status: set.(*Order).Status} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Order) Quantity() int {
 | 
			
		||||
	return len(o.Purchases) + len(o.Purchases)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Order) SetName() {
 | 
			
		||||
	d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Order) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								models/order/order_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								models/order/order_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package order
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type orderMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the orderMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *orderMongoAccessor {
 | 
			
		||||
	return &orderMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.ORDER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *orderMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Order{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data,a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return nil, 404, errors.New("Not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Order](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Order](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Order](filters, search, (&Order{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								models/order/tests/order_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/order/tests/order_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package tests
 | 
			
		||||
							
								
								
									
										91
									
								
								models/peer/peer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								models/peer/peer.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
package peer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// now write a go enum for the state partner with self, blacklist, partner
 | 
			
		||||
 | 
			
		||||
type PeerState int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	NONE PeerState = iota
 | 
			
		||||
	SELF
 | 
			
		||||
	PARTNER
 | 
			
		||||
	BLACKLIST
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m PeerState) String() string {
 | 
			
		||||
	return [...]string{"NONE", "SELF", "PARTNER", "BLACKLIST"}[m]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m PeerState) EnumIndex() int {
 | 
			
		||||
	return int(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Peer is a struct that represents a peer
 | 
			
		||||
type Peer struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Url             string          `json:"url" bson:"url" validate:"required"`                       // Url is the URL of the peer (base64url)
 | 
			
		||||
	WalletAddress   string          `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
 | 
			
		||||
	PublicKey       string          `json:"public_key" bson:"public_key" validate:"required"`         // PublicKey is the public key of the peer
 | 
			
		||||
	State           PeerState       `json:"state" bson:"state" default:"0"`
 | 
			
		||||
	ServicesState   map[string]int  `json:"services_state,omitempty" bson:"services_state,omitempty"`
 | 
			
		||||
	FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Peer) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddExecution adds an execution to the list of failed executions
 | 
			
		||||
func (ao *Peer) AddExecution(exec PeerExecution) {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, v := range ao.FailedExecution { // Check if the execution is already in the list
 | 
			
		||||
		if v.Url == exec.Url && v.Method == exec.Method && fmt.Sprint(v.Body) == fmt.Sprint(exec.Body) {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		ao.FailedExecution = append(ao.FailedExecution, exec)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveExecution removes an execution from the list of failed executions
 | 
			
		||||
func (ao *Peer) RemoveExecution(exec PeerExecution) {
 | 
			
		||||
	new := []PeerExecution{}
 | 
			
		||||
	for i, v := range ao.FailedExecution {
 | 
			
		||||
		if !(v.Url == exec.Url && v.Method == exec.Method && fmt.Sprint(v.Body) == fmt.Sprint(exec.Body)) {
 | 
			
		||||
			new = append(new, ao.FailedExecution[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ao.FailedExecution = new
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsMySelf checks if the peer is the local peer
 | 
			
		||||
func (p *Peer) IsMySelf() (bool, string) {
 | 
			
		||||
	d, code, err := NewAccessor(nil).Search(nil, SELF.String(), p.IsDraft)
 | 
			
		||||
	if code != 200 || err != nil || len(d) == 0 {
 | 
			
		||||
		return false, ""
 | 
			
		||||
	}
 | 
			
		||||
	id := d[0].GetID()
 | 
			
		||||
	return p.UUID == id, id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LaunchPeerExecution launches an execution on a peer
 | 
			
		||||
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (map[string]interface{}, error) {
 | 
			
		||||
	p.UUID = peerID
 | 
			
		||||
	return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache
 | 
			
		||||
}
 | 
			
		||||
func (d *Peer) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	data := NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Peer) CanDelete() bool {
 | 
			
		||||
	return false // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								models/peer/peer_cache.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								models/peer/peer_cache.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
package peer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* PeerExecution is a struct that represents an execution on a peer
 | 
			
		||||
* it defines the execution data
 | 
			
		||||
 */
 | 
			
		||||
type PeerExecution struct {
 | 
			
		||||
	Method   string      `json:"method" bson:"method"`
 | 
			
		||||
	Url      string      `json:"url" bson:"url"`
 | 
			
		||||
	Body     interface{} `json:"body" bson:"body"`
 | 
			
		||||
	DataType int         `json:"data_type" bson:"data_type"`
 | 
			
		||||
	DataID   string      `json:"data_id" bson:"data_id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var cache = &PeerCache{} // Singleton instance of the peer cache
 | 
			
		||||
// PeerCache is a struct that represents a peer cache
 | 
			
		||||
type PeerCache struct {
 | 
			
		||||
	Executions []*PeerExecution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// urlFormat formats the URL of the peer with the data type API function
 | 
			
		||||
func (p *PeerCache) urlFormat(hostUrl string, dt tools.DataType) string {
 | 
			
		||||
	return hostUrl + "/" + strings.ReplaceAll(dt.API(), "oc-", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkPeerStatus checks the status of a peer
 | 
			
		||||
func (p *PeerCache) checkPeerStatus(peerID string, appName string) (*Peer, bool) {
 | 
			
		||||
	api := tools.API{}
 | 
			
		||||
	access := NewShallowAccessor()
 | 
			
		||||
	res, code, _ := access.LoadOne(peerID) // Load the peer from db
 | 
			
		||||
	if code != 200 {                       // no peer no party
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	url := p.urlFormat(res.(*Peer).Url, tools.PEER) + "/status" // Format the URL
 | 
			
		||||
	state, services := api.CheckRemotePeer(url)
 | 
			
		||||
	res.(*Peer).ServicesState = services                              // Update the services states of the peer
 | 
			
		||||
	access.UpdateOne(res, peerID)                                     // Update the peer in the db
 | 
			
		||||
	return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LaunchPeerExecution launches an execution on a peer
 | 
			
		||||
// The method contacts the path described by : peer.Url + datatype path (from enums) + replacement of id by dataID
 | 
			
		||||
func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
 | 
			
		||||
	dt tools.DataType, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) (map[string]interface{}, error) {
 | 
			
		||||
	fmt.Println("Launching peer execution on", caller.GetUrls(), dt, method)
 | 
			
		||||
	methods := caller.GetUrls()[dt] // Get the methods url of the data type
 | 
			
		||||
	if m, ok := methods[method]; !ok || m == "" {
 | 
			
		||||
		return map[string]interface{}{}, errors.New("Requested method " + method.String() + " not declared in HTTPCaller")
 | 
			
		||||
	}
 | 
			
		||||
	path := methods[method]                        // Get the path corresponding to the action we want to execute
 | 
			
		||||
	path = strings.ReplaceAll(path, ":id", dataID) // Replace the id in the path in case of a DELETE / UPDATE method (it's a standard naming in OC)
 | 
			
		||||
	url := ""
 | 
			
		||||
 | 
			
		||||
	// Check the status of the peer
 | 
			
		||||
	if mypeer, ok := p.checkPeerStatus(peerID, dt.API()); !ok && mypeer != nil {
 | 
			
		||||
		// If the peer is not reachable, add the execution to the failed executions list
 | 
			
		||||
		pexec := &PeerExecution{
 | 
			
		||||
			Method:   method.String(),
 | 
			
		||||
			Url:      p.urlFormat((mypeer.Url), dt) + path, // the url is constitued of : host URL + resource path + action path (ex : mypeer.com/datacenter/resourcetype/path/to/action)
 | 
			
		||||
			Body:     body,
 | 
			
		||||
			DataType: dt.EnumIndex(),
 | 
			
		||||
			DataID:   dataID,
 | 
			
		||||
		}
 | 
			
		||||
		mypeer.AddExecution(*pexec)
 | 
			
		||||
		NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		return map[string]interface{}{}, errors.New("peer is " + peerID + " not reachable")
 | 
			
		||||
	} else {
 | 
			
		||||
		if mypeer == nil {
 | 
			
		||||
			return map[string]interface{}{}, errors.New("peer " + peerID + " not found")
 | 
			
		||||
		}
 | 
			
		||||
		// If the peer is reachable, launch the execution
 | 
			
		||||
		url = p.urlFormat((mypeer.Url), dt) + path     // Format the URL
 | 
			
		||||
		tmp := mypeer.FailedExecution                  // Get the failed executions list
 | 
			
		||||
		mypeer.FailedExecution = []PeerExecution{}     // Reset the failed executions list
 | 
			
		||||
		NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		for _, v := range tmp {                        // Retry the failed executions
 | 
			
		||||
			go p.Exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return p.Exec(url, method, body, caller) // Execute the method
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// exec executes the method on the peer
 | 
			
		||||
func (p *PeerCache) Exec(url string, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) (map[string]interface{}, error) {
 | 
			
		||||
	var b []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if method == tools.POST { // Execute the POST method if it's a POST method
 | 
			
		||||
		b, err = caller.CallPost(url, "", body)
 | 
			
		||||
	}
 | 
			
		||||
	if method == tools.GET { // Execute the GET method if it's a GET method
 | 
			
		||||
		b, err = caller.CallGet(url, "")
 | 
			
		||||
	}
 | 
			
		||||
	if method == tools.DELETE { // Execute the DELETE method if it's a DELETE method
 | 
			
		||||
		b, err = caller.CallDelete(url, "")
 | 
			
		||||
	}
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return m, err
 | 
			
		||||
	}
 | 
			
		||||
	err = json.Unmarshal(b, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return m, err
 | 
			
		||||
	}
 | 
			
		||||
	if e, ok := m["error"]; ok && e != "<nil>" && e != "" { // Check if there is an error in the response
 | 
			
		||||
		return m, errors.New(fmt.Sprintf("%v", m["error"]))
 | 
			
		||||
	}
 | 
			
		||||
	return m, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										99
									
								
								models/peer/peer_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								models/peer/peer_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
package peer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type peerMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	OverrideAuth           bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the peerMongoAccessor
 | 
			
		||||
func NewShallowAccessor() *peerMongoAccessor {
 | 
			
		||||
	return &peerMongoAccessor{
 | 
			
		||||
		OverrideAuth: true,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
 | 
			
		||||
			Type:   tools.PEER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
 | 
			
		||||
	return &peerMongoAccessor{
 | 
			
		||||
		OverrideAuth: false,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.PEER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	return !wfa.OverrideAuth
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set.(*Peer), id, wfa, &Peer{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Peer), wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Peer](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Peer](filters, search, wfa.GetDefaultFilter(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
 | 
			
		||||
	if i, err := strconv.Atoi(search); err == nil {
 | 
			
		||||
		return &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // search by name if no filters are provided
 | 
			
		||||
				"state": {{Operator: dbs.EQUAL.String(), Value: i}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if search == "*" {
 | 
			
		||||
			search = ""
 | 
			
		||||
		}
 | 
			
		||||
		return &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // search by name if no filters are provided
 | 
			
		||||
				"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"url":                 {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								models/peer/tests/peer_cache_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								models/peer/tests/peer_cache_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
package peer_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MockHTTPCaller mocks tools.HTTPCaller
 | 
			
		||||
type MockHTTPCaller struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	URLS map[tools.DataType]map[tools.METHOD]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MockHTTPCaller) GetUrls() map[tools.DataType]map[tools.METHOD]string {
 | 
			
		||||
	return c.URLS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallPost(url, token string, body interface{}, types ...string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token, body)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallGet(url, token string, types ...string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallDelete(url, token string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLaunchPeerExecution_PeerNotReachable(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{
 | 
			
		||||
		URLS: map[tools.DataType]map[tools.METHOD]string{
 | 
			
		||||
			tools.PEER: {
 | 
			
		||||
				tools.POST: "/execute/:id",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	exec, err := cache.LaunchPeerExecution("peer-id", "data-id", tools.PEER, tools.POST, map[string]string{"a": "b"}, caller)
 | 
			
		||||
	assert.Nil(t, exec)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "not reachable")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecSuccess(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	response := map[string]interface{}{"result": "ok"}
 | 
			
		||||
	data, _ := json.Marshal(response)
 | 
			
		||||
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	caller.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecReturnsErrorField(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	response := map[string]interface{}{"error": "something failed"}
 | 
			
		||||
	data, _ := json.Marshal(response)
 | 
			
		||||
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "something failed", err.Error())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecInvalidJSON(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return([]byte("{invalid json}"), nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "invalid character")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mockAccessor struct {
 | 
			
		||||
	loadOne   func(string) (interface{}, int, error)
 | 
			
		||||
	updateOne func(interface{}, string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockAccessor) LoadOne(id string) (interface{}, int, error) {
 | 
			
		||||
	return m.loadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockAccessor) UpdateOne(i interface{}, id string) error {
 | 
			
		||||
	return m.updateOne(i, id)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								models/peer/tests/peer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								models/peer/tests/peer_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
package peer_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(set, id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, search, isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTestPeer() *peer.Peer {
 | 
			
		||||
	return &peer.Peer{
 | 
			
		||||
		Url:           "http://localhost",
 | 
			
		||||
		WalletAddress: "0x123",
 | 
			
		||||
		PublicKey:     "pubkey",
 | 
			
		||||
		State:         peer.SELF,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	mockAcc.On("DeleteOne", "id").Return(newTestPeer(), 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.DeleteOne("id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.NotNil(t, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	peerObj := newTestPeer()
 | 
			
		||||
	mockAcc.On("UpdateOne", peerObj, "id").Return(peerObj, 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.UpdateOne(peerObj, "id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, peerObj, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	peerObj := newTestPeer()
 | 
			
		||||
	mockAcc.On("StoreOne", peerObj).Return(peerObj, 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.StoreOne(peerObj)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, peerObj, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	mockAcc.On("LoadOne", "test-id").Return(newTestPeer(), 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.LoadOne("test-id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.NotNil(t, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadAll_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	expected := []utils.ShallowDBObject{newTestPeer()}
 | 
			
		||||
	mockAcc.On("LoadAll", false).Return(expected, 200, nil)
 | 
			
		||||
 | 
			
		||||
	objs, code, err := mockAcc.LoadAll(false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, expected, objs)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSearch_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	filters := &dbs.Filters{}
 | 
			
		||||
	expected := []utils.ShallowDBObject{newTestPeer()}
 | 
			
		||||
	mockAcc.On("Search", filters, "test", false).Return(expected, 200, nil)
 | 
			
		||||
 | 
			
		||||
	objs, code, err := mockAcc.Search(filters, "test", false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, expected, objs)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										203
									
								
								models/resources/compute.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										203
									
								
								models/resources/compute.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,203 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ComputeResource is a struct that represents a compute resource
 | 
			
		||||
* it defines the resource compute
 | 
			
		||||
 */
 | 
			
		||||
type ComputeResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*ComputeResourceInstance]
 | 
			
		||||
	Architecture   string                  `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
 | 
			
		||||
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"`    // Infrastructure is the infrastructure
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ComputeResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &ComputeResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ComputeResource) GetType() string {
 | 
			
		||||
	return tools.COMPUTE_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedComputeResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeNode struct {
 | 
			
		||||
	Name     string           `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Quantity int64            `json:"quantity" bson:"quantity" default:"1"`
 | 
			
		||||
	RAM      *models.RAM      `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
 | 
			
		||||
	CPUs     map[string]int64 `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs     map[string]int64 `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourceInstance struct {
 | 
			
		||||
	ResourceInstance[*ComputeResourcePartnership]
 | 
			
		||||
	Source             string                 `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the resource
 | 
			
		||||
	SecurityLevel      string                 `json:"security_level,omitempty" bson:"security_level,omitempty"`
 | 
			
		||||
	PowerSources       []string               `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
 | 
			
		||||
	AnnualCO2Emissions float64                `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
 | 
			
		||||
	CPUs               map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs               map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	Nodes              []*ComputeNode         `json:"nodes,omitempty" bson:"nodes,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*ComputeResourcePricingProfile]
 | 
			
		||||
	MinGaranteedCPUsCores    map[string]float64 `json:"garanteed_cpus,omitempty" bson:"garanteed_cpus,omitempty"`
 | 
			
		||||
	MinGaranteedGPUsMemoryGB map[string]float64 `json:"garanteed_gpus,omitempty" bson:"garanteed_gpus,omitempty"`
 | 
			
		||||
	MinGaranteedRAMSize      float64            `json:"garanteed_ram,omitempty" bson:"garanteed_ram,omitempty"`
 | 
			
		||||
 | 
			
		||||
	MaxAllowedCPUsCores    map[string]float64 `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"`
 | 
			
		||||
	MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"`
 | 
			
		||||
	MaxAllowedRAMSize      float64            `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourcePricingProfile struct {
 | 
			
		||||
	pricing.ExploitPricingProfile[pricing.TimePricingStrategy]
 | 
			
		||||
	// ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time
 | 
			
		||||
	CPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs
 | 
			
		||||
	GPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs
 | 
			
		||||
	RAMPrice   float64            `json:"ram_price" bson:"ram_price" default:"-1"`            // RAMPrice is the price of the RAM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	if p.Pricing.BuyingStrategy == pricing.PERMANENT {
 | 
			
		||||
		p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOT A PROPER QUANTITY
 | 
			
		||||
// amountOfData is the number of CPUs, GPUs or RAM dependings on the params
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	if len(params) < 1 {
 | 
			
		||||
		return 0, errors.New("params must be set")
 | 
			
		||||
	}
 | 
			
		||||
	pp := float64(0)
 | 
			
		||||
	model := params[1]
 | 
			
		||||
	if strings.Contains(params[0], "cpus") && len(params) > 1 {
 | 
			
		||||
		if _, ok := p.CPUsPrices[model]; ok {
 | 
			
		||||
			p.Pricing.Price = p.CPUsPrices[model]
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(params[0], "gpus") && len(params) > 1 {
 | 
			
		||||
		if _, ok := p.GPUsPrices[model]; ok {
 | 
			
		||||
			p.Pricing.Price = p.GPUsPrices[model]
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(params[0], "ram") {
 | 
			
		||||
		if p.RAMPrice >= 0 {
 | 
			
		||||
			p.Pricing.Price = p.RAMPrice
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(float64(amountOfData), explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
	}
 | 
			
		||||
	return pp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedComputeResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
 | 
			
		||||
	CPUsLocated map[string]float64 `json:"cpus_in_use" bson:"cpus_in_use"` // CPUsInUse is the list of CPUs in use
 | 
			
		||||
	GPUsLocated map[string]float64 `json:"gpus_in_use" bson:"gpus_in_use"` // GPUsInUse is the list of GPUs in use
 | 
			
		||||
	RAMLocated  float64            `json:"ram_in_use" bson:"ram_in_use"`   // RAMInUse is the RAM in use
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedComputeResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.COMPUTE_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedComputeResource) GetPrice() (float64, error) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	price := float64(0)
 | 
			
		||||
	for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
 | 
			
		||||
		for model, amountOfData := range l {
 | 
			
		||||
			cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "cpus", model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			price += cpus
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "ram")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	price += ram
 | 
			
		||||
	return price, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* FillWithDefaultProcessingUsage fills the order item with the default processing usage
 | 
			
		||||
* it depends on the processing usage only if nothing is set, during order
 | 
			
		||||
 */
 | 
			
		||||
func (i *PricedComputeResource) FillWithDefaultProcessingUsage(usage *ProcessingUsage) {
 | 
			
		||||
	for _, cpu := range usage.CPUs {
 | 
			
		||||
		if _, ok := i.CPUsLocated[cpu.Model]; !ok {
 | 
			
		||||
			i.CPUsLocated[cpu.Model] = 0
 | 
			
		||||
		}
 | 
			
		||||
		if i.CPUsLocated[cpu.Model] < float64(cpu.Cores) {
 | 
			
		||||
			i.CPUsLocated[cpu.Model] = float64(cpu.Cores)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cpu := range usage.GPUs {
 | 
			
		||||
		i.GPUsLocated[cpu.Model] = 1
 | 
			
		||||
	}
 | 
			
		||||
	i.RAMLocated = usage.RAM.SizeGb
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										188
									
								
								models/resources/data.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										188
									
								
								models/resources/data.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* DataResource is a struct that represents a data resource
 | 
			
		||||
* it defines the resource data
 | 
			
		||||
 */
 | 
			
		||||
type DataResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*DataInstance]
 | 
			
		||||
	Type                   string     `bson:"type,omitempty" json:"type,omitempty"`
 | 
			
		||||
	Quality                string     `bson:"quality,omitempty" json:"quality,omitempty"`
 | 
			
		||||
	OpenData               bool       `bson:"open_data" json:"open_data" default:"false"` // Type is the type of the storage
 | 
			
		||||
	Static                 bool       `bson:"static" json:"static" default:"false"`
 | 
			
		||||
	UpdatePeriod           *time.Time `bson:"update_period,omitempty" json:"update_period,omitempty"`
 | 
			
		||||
	PersonalData           bool       `bson:"personal_data,omitempty" json:"personal_data,omitempty"`
 | 
			
		||||
	AnonymizedPersonalData bool       `bson:"anonymized_personal_data,omitempty" json:"anonymized_personal_data,omitempty"`
 | 
			
		||||
	SizeGB                 float64    `json:"size,omitempty" bson:"size,omitempty"` // SizeGB is the size of the data	License              DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data
 | 
			
		||||
	// ? Interest               DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"`      // Interest is the interest of the data
 | 
			
		||||
	Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*DataResource](tools.DATA_RESOURCE, request, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DataResource) GetType() string {
 | 
			
		||||
	return tools.DATA_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.DATA_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedDataResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataInstance struct {
 | 
			
		||||
	ResourceInstance[*DataResourcePartnership]
 | 
			
		||||
	Source string `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *DataInstance) StoreDraftDefault() {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, p := range ri.ResourceInstance.Env {
 | 
			
		||||
		if p.Attr == "source" {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		ri.ResourceInstance.Env = append(ri.ResourceInstance.Env, models.Param{
 | 
			
		||||
			Attr:     "source",
 | 
			
		||||
			Value:    ri.Source,
 | 
			
		||||
			Readonly: true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	ri.ResourceInstance.StoreDraftDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*DataResourcePricingProfile]
 | 
			
		||||
	MaxDownloadableGbAllowed      float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
 | 
			
		||||
	PersonalDataAllowed           bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
 | 
			
		||||
	AnonymizedPersonalDataAllowed bool    `json:"anonymized_personal_data_allowed,omitempty" bson:"anonymized_personal_data_allowed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PER_DOWNLOAD DataResourcePricingStrategy = iota + 6
 | 
			
		||||
	PER_TB_DOWNLOADED
 | 
			
		||||
	PER_GB_DOWNLOADED
 | 
			
		||||
	PER_MB_DOWNLOADED
 | 
			
		||||
	PER_KB_DOWNLOADED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) String() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
 | 
			
		||||
	return []DataResourcePricingStrategy{PER_DOWNLOAD, PER_TB_DOWNLOADED, PER_GB_DOWNLOADED, PER_MB_DOWNLOADED, PER_KB_DOWNLOADED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
 | 
			
		||||
	return DataResourcePricingStrategy(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetStrategy() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PER_DOWNLOAD:
 | 
			
		||||
		return 1, nil
 | 
			
		||||
	case PER_TB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB * 1000, nil
 | 
			
		||||
	case PER_GB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_MB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB / 1000, nil
 | 
			
		||||
	case PER_KB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB / 1000000, nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePricingProfile struct {
 | 
			
		||||
	pricing.AccessPricingProfile[DataResourcePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
 | 
			
		||||
	return p.Pricing.OverrideStrategy.GetStrategyValue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	// TODO WHAT ABOUT PAY PER USE... it's a complicate CASE
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.PERMANENT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedDataResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedDataResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.DATA_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedDataResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	var err error
 | 
			
		||||
	amountOfData := float64(1)
 | 
			
		||||
	if pricing.GetOverrideStrategyValue() >= 0 {
 | 
			
		||||
		amountOfData, err = ToDataResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/resources/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										38
									
								
								models/resources/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceInterface interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	Trim()
 | 
			
		||||
	ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF
 | 
			
		||||
	GetType() string
 | 
			
		||||
	GetSelectedInstance() ResourceInstanceITF
 | 
			
		||||
	ClearEnv() utils.DBObject
 | 
			
		||||
	SetAllowedInstances(request *tools.APIRequest)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourceInstanceITF interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	StoreDraftDefault()
 | 
			
		||||
	ClearEnv()
 | 
			
		||||
	GetProfile() pricing.PricingProfileITF
 | 
			
		||||
	GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
 | 
			
		||||
	GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
 | 
			
		||||
	ClearPeerGroups()
 | 
			
		||||
	GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF
 | 
			
		||||
	GetPartnerships(peerID string, groups []string) []ResourcePartnerITF
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourcePartnerITF interface {
 | 
			
		||||
	GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
 | 
			
		||||
	GetPeerGroups() map[string][]string
 | 
			
		||||
	ClearPeerGroups()
 | 
			
		||||
	GetProfile(buying int, strategy int) pricing.PricingProfileITF
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								models/resources/models.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										65
									
								
								models/resources/models.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceSet struct {
 | 
			
		||||
	Datas       []string `bson:"datas,omitempty" json:"datas,omitempty"`
 | 
			
		||||
	Storages    []string `bson:"storages,omitempty" json:"storages,omitempty"`
 | 
			
		||||
	Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
 | 
			
		||||
	Computes    []string `bson:"computes,omitempty" json:"computes,omitempty"`
 | 
			
		||||
	Workflows   []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
 | 
			
		||||
 | 
			
		||||
	DataResources       []*DataResource       `bson:"-" json:"data_resources,omitempty"`
 | 
			
		||||
	StorageResources    []*StorageResource    `bson:"-" json:"storage_resources,omitempty"`
 | 
			
		||||
	ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
 | 
			
		||||
	ComputeResources    []*ComputeResource    `bson:"-" json:"compute_resources,omitempty"`
 | 
			
		||||
	WorkflowResources   []*WorkflowResource   `bson:"-" json:"workflow_resources,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceSet) Clear() {
 | 
			
		||||
	r.DataResources = nil
 | 
			
		||||
	r.StorageResources = nil
 | 
			
		||||
	r.ProcessingResources = nil
 | 
			
		||||
	r.ComputeResources = nil
 | 
			
		||||
	r.WorkflowResources = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceSet) Fill(request *tools.APIRequest) {
 | 
			
		||||
	r.Clear()
 | 
			
		||||
	for k, v := range map[utils.DBObject][]string{
 | 
			
		||||
		(&DataResource{}):       r.Datas,
 | 
			
		||||
		(&ComputeResource{}):    r.Computes,
 | 
			
		||||
		(&StorageResource{}):    r.Storages,
 | 
			
		||||
		(&ProcessingResource{}): r.Processings,
 | 
			
		||||
		(&WorkflowResource{}):   r.Workflows,
 | 
			
		||||
	} {
 | 
			
		||||
		for _, id := range v {
 | 
			
		||||
			d, _, e := k.GetAccessor(request).LoadOne(id)
 | 
			
		||||
			if e == nil {
 | 
			
		||||
				switch k.(type) {
 | 
			
		||||
				case *DataResource:
 | 
			
		||||
					r.DataResources = append(r.DataResources, d.(*DataResource))
 | 
			
		||||
				case *ComputeResource:
 | 
			
		||||
					r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource))
 | 
			
		||||
				case *StorageResource:
 | 
			
		||||
					r.StorageResources = append(r.StorageResources, d.(*StorageResource))
 | 
			
		||||
				case *ProcessingResource:
 | 
			
		||||
					r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource))
 | 
			
		||||
				case *WorkflowResource:
 | 
			
		||||
					r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ItemResource struct {
 | 
			
		||||
	Data       *DataResource       `bson:"data,omitempty" json:"data,omitempty"`
 | 
			
		||||
	Processing *ProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"`
 | 
			
		||||
	Storage    *StorageResource    `bson:"storage,omitempty" json:"storage,omitempty"`
 | 
			
		||||
	Compute    *ComputeResource    `bson:"compute,omitempty" json:"compute,omitempty"`
 | 
			
		||||
	Workflow   *WorkflowResource   `bson:"workflow,omitempty" json:"workflow,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								models/resources/priced_resource.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								models/resources/priced_resource.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricedResource struct {
 | 
			
		||||
	Name                     string                    `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Logo                     string                    `json:"logo,omitempty" bson:"logo,omitempty"`
 | 
			
		||||
	InstancesRefs            map[string]string         `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
 | 
			
		||||
	SelectedPricing          pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
 | 
			
		||||
	ExplicitBookingDurationS float64                   `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
 | 
			
		||||
	UsageStart               *time.Time                `json:"start,omitempty" bson:"start,omitempty"`
 | 
			
		||||
	UsageEnd                 *time.Time                `json:"end,omitempty" bson:"end,omitempty"`
 | 
			
		||||
	CreatorID                string                    `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
 | 
			
		||||
	ResourceID               string                    `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
 | 
			
		||||
	ResourceType             tools.DataType            `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF {
 | 
			
		||||
	return abs.SelectedPricing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetID() string {
 | 
			
		||||
	return abs.ResourceID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetType() tools.DataType {
 | 
			
		||||
	return abs.ResourceType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetCreatorID() string {
 | 
			
		||||
	return abs.CreatorID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) IsPurchasable() bool {
 | 
			
		||||
	if abs.SelectedPricing == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return (abs.SelectedPricing).IsPurchasable()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) IsBooked() bool {
 | 
			
		||||
	if abs.SelectedPricing == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return (abs.SelectedPricing).IsBooked()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetLocationEnd() *time.Time {
 | 
			
		||||
	return abs.UsageEnd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetLocationStart() *time.Time {
 | 
			
		||||
	return abs.UsageStart
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SetLocationStart(start time.Time) {
 | 
			
		||||
	abs.UsageStart = &start
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SetLocationEnd(end time.Time) {
 | 
			
		||||
	abs.UsageEnd = &end
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetExplicitDurationInS() float64 {
 | 
			
		||||
	if abs.ExplicitBookingDurationS == 0 {
 | 
			
		||||
		if abs.UsageEnd == nil && abs.UsageStart == nil {
 | 
			
		||||
			return time.Duration(1 * time.Hour).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		if abs.UsageEnd == nil {
 | 
			
		||||
			add := abs.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
			abs.UsageEnd = &add
 | 
			
		||||
		}
 | 
			
		||||
		return abs.UsageEnd.Sub(*abs.UsageStart).Seconds()
 | 
			
		||||
	}
 | 
			
		||||
	return abs.ExplicitBookingDurationS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								models/resources/processing.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										94
									
								
								models/resources/processing.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ProcessingUsage struct {
 | 
			
		||||
	CPUs map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	RAM  *models.RAM            `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
 | 
			
		||||
 | 
			
		||||
	StorageGb    float64 `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage
 | 
			
		||||
	Hypothesis   string  `bson:"hypothesis,omitempty" json:"hypothesis,omitempty"`
 | 
			
		||||
	ScalingModel string  `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ProcessingResource is a struct that represents a processing resource
 | 
			
		||||
* it defines the resource processing
 | 
			
		||||
 */
 | 
			
		||||
type ProcessingResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*ProcessingInstance]
 | 
			
		||||
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure
 | 
			
		||||
	IsService      bool                    `json:"is_service,omitempty" bson:"is_service,omitempty"`  // IsService is a flag that indicates if the processing is a service
 | 
			
		||||
	Usage          *ProcessingUsage        `bson:"usage,omitempty" json:"usage,omitempty"`            // Usage is the usage of the processing
 | 
			
		||||
	OpenSource     bool                    `json:"open_source" bson:"open_source" default:"false"`
 | 
			
		||||
	License        string                  `json:"license,omitempty" bson:"license,omitempty"`
 | 
			
		||||
	Maturity       string                  `json:"maturity,omitempty" bson:"maturity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ProcessingResource) GetType() string {
 | 
			
		||||
	return tools.PROCESSING_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingResourceAccess struct {
 | 
			
		||||
	Container *models.Container `json:"container,omitempty" bson:"container,omitempty"` // Container is the container
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingInstance struct {
 | 
			
		||||
	ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]
 | 
			
		||||
	Access *ProcessingResourceAccess `json:"access,omitempty" bson:"access,omitempty"` // Access is the access
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedProcessingResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	IsService bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedProcessingResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.PROCESSING_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PricedProcessingResource) GetExplicitDurationInS() float64 {
 | 
			
		||||
	if a.ExplicitBookingDurationS == 0 {
 | 
			
		||||
		if a.IsService || a.UsageStart == nil {
 | 
			
		||||
			if a.IsService {
 | 
			
		||||
				return -1
 | 
			
		||||
			}
 | 
			
		||||
			return time.Duration(1 * time.Hour).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		return a.UsageEnd.Sub(*a.UsageStart).Seconds()
 | 
			
		||||
	}
 | 
			
		||||
	return a.ExplicitBookingDurationS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*ProcessingResource](tools.PROCESSING_RESOURCE, request, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingResourcePricingProfile struct {
 | 
			
		||||
	pricing.AccessPricingProfile[pricing.TimePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.PERMANENT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, val, start, &end)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								models/resources/purchase_resource/purchase_resource.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								models/resources/purchase_resource/purchase_resource.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
package purchase_resource
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PurchaseResource struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	DestPeerID   string
 | 
			
		||||
	PricedItem   pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
 | 
			
		||||
	ExecutionsID string                `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
 | 
			
		||||
	EndDate      *time.Time            `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
 | 
			
		||||
	ResourceID   string                `json:"resource_id" bson:"resource_id" validate:"required"`
 | 
			
		||||
	ResourceType tools.DataType        `json:"resource_type" bson:"resource_type" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PurchaseResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	return r.IsDraft, set // only draft buying can be updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PurchaseResource) CanDelete() bool { // ENDBuyingDate is passed
 | 
			
		||||
	if r.EndDate != nil {
 | 
			
		||||
		return time.Now().UTC().After(*r.EndDate)
 | 
			
		||||
	}
 | 
			
		||||
	return false // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
package purchase_resource
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PurchaseResourceMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the bookingMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *PurchaseResourceMongoAccessor {
 | 
			
		||||
	return &PurchaseResourceMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.PURCHASE_RESOURCE.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.PURCHASE_RESOURCE,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &PurchaseResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*PurchaseResource](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
 | 
			
		||||
			utils.GenericDeleteOne(id, a)
 | 
			
		||||
			return nil, 404, nil
 | 
			
		||||
		}
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*PurchaseResource](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*PurchaseResource](filters, search, (&PurchaseResource{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package purchase_resource_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	res := &purchase_resource.PurchaseResource{}
 | 
			
		||||
	accessor := res.GetAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.PURCHASE_RESOURCE, accessor.(*purchase_resource.PurchaseResourceMongoAccessor).Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate(t *testing.T) {
 | 
			
		||||
	set := &purchase_resource.PurchaseResource{ResourceID: "id"}
 | 
			
		||||
	r := &purchase_resource.PurchaseResource{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{IsDraft: true},
 | 
			
		||||
	}
 | 
			
		||||
	can, updated := r.CanUpdate(set)
 | 
			
		||||
	assert.True(t, can)
 | 
			
		||||
	assert.Equal(t, set, updated)
 | 
			
		||||
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
	can, _ = r.CanUpdate(set)
 | 
			
		||||
	assert.False(t, can)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete(t *testing.T) {
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	past := now.Add(-1 * time.Hour)
 | 
			
		||||
	future := now.Add(1 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	t.Run("nil EndDate", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{}
 | 
			
		||||
		assert.False(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("EndDate in past", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{EndDate: &past}
 | 
			
		||||
		assert.True(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("EndDate in future", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{EndDate: &future}
 | 
			
		||||
		assert.False(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										266
									
								
								models/resources/resource.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										266
									
								
								models/resources/resource.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,266 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/biter777/countries"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AbstractResource is the struct containing all of the attributes commons to all ressources
 | 
			
		||||
type AbstractResource struct {
 | 
			
		||||
	utils.AbstractObject                // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	Type                  string        `json:"type,omitempty" bson:"type,omitempty"`                                               // Type is the type of the resource
 | 
			
		||||
	Logo                  string        `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"`                           // Logo is the logo of the resource
 | 
			
		||||
	Description           string        `json:"description,omitempty" bson:"description,omitempty"`                                 // Description is the description of the resource
 | 
			
		||||
	ShortDescription      string        `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
 | 
			
		||||
	Owners                []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"`                                           // Owners is the list of owners of the resource
 | 
			
		||||
	UsageRestrictions     string        `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
 | 
			
		||||
	SelectedInstanceIndex *int          `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) GetType() string {
 | 
			
		||||
	return tools.INVALID.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if r.IsDraft != set.IsDrafted() && set.IsDrafted() {
 | 
			
		||||
		return true, set // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
 | 
			
		||||
	AbstractResource     // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	Instances        []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource            // Bill is the bill of the resource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	instances := map[string]string{}
 | 
			
		||||
	profiles := []pricing.PricingProfileITF{}
 | 
			
		||||
	for _, instance := range abs.Instances {
 | 
			
		||||
		instances[instance.GetID()] = instance.GetName()
 | 
			
		||||
		profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
 | 
			
		||||
	}
 | 
			
		||||
	var profile pricing.PricingProfileITF
 | 
			
		||||
	if t := abs.GetSelectedInstance(); t != nil {
 | 
			
		||||
		profile = t.GetProfile()
 | 
			
		||||
	}
 | 
			
		||||
	if profile == nil && len(profiles) > 0 {
 | 
			
		||||
		profile = profiles[0]
 | 
			
		||||
	}
 | 
			
		||||
	return &PricedResource{
 | 
			
		||||
		Name:            abs.Name,
 | 
			
		||||
		Logo:            abs.Logo,
 | 
			
		||||
		ResourceID:      abs.UUID,
 | 
			
		||||
		ResourceType:    t,
 | 
			
		||||
		InstancesRefs:   instances,
 | 
			
		||||
		SelectedPricing: profile,
 | 
			
		||||
		CreatorID:       abs.CreatorID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
 | 
			
		||||
	for _, instance := range abs.Instances {
 | 
			
		||||
		instance.ClearEnv()
 | 
			
		||||
	}
 | 
			
		||||
	return abs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF {
 | 
			
		||||
	if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
 | 
			
		||||
		return r.Instances[*r.SelectedInstanceIndex]
 | 
			
		||||
	}
 | 
			
		||||
	if len(r.Instances) > 0 {
 | 
			
		||||
		return r.Instances[0]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) {
 | 
			
		||||
	if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	abs.Instances = VerifyAuthAction[T](abs.Instances, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractInstanciatedResource[T]) Trim() {
 | 
			
		||||
	d.Type = d.GetType()
 | 
			
		||||
	if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok {
 | 
			
		||||
		for _, instance := range d.Instances {
 | 
			
		||||
			instance.ClearPeerGroups()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
 | 
			
		||||
	instances := []T{}
 | 
			
		||||
	for _, instance := range baseInstance {
 | 
			
		||||
		_, peerGroups := instance.GetPeerGroups()
 | 
			
		||||
		for _, peers := range peerGroups {
 | 
			
		||||
			if request == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist {
 | 
			
		||||
				if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
 | 
			
		||||
					instances = append(instances, instance)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				for _, grp := range grps {
 | 
			
		||||
					if slices.Contains(request.Groups, grp) {
 | 
			
		||||
						instances = append(instances, instance)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return instances
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GeoPoint struct {
 | 
			
		||||
	Latitude  float64 `json:"latitude,omitempty" bson:"latitude,omitempty"`
 | 
			
		||||
	Longitude float64 `json:"longitude,omitempty" bson:"longitude,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourceInstance[T ResourcePartnerITF] struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Location                 GeoPoint              `json:"location,omitempty" bson:"location,omitempty"`
 | 
			
		||||
	Country                  countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
 | 
			
		||||
	AccessProtocol           string                `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
 | 
			
		||||
	Env                      []models.Param        `json:"env,omitempty" bson:"env,omitempty"`
 | 
			
		||||
	Inputs                   []models.Param        `json:"inputs,omitempty" bson:"inputs,omitempty"`
 | 
			
		||||
	Outputs                  []models.Param        `json:"outputs,omitempty" bson:"outputs,omitempty"`
 | 
			
		||||
	SelectedPartnershipIndex int                   `json:"selected_partnership_index,omitempty" bson:"selected_partnership_index,omitempty"`
 | 
			
		||||
	SelectedBuyingStrategy   int                   `json:"selected_buying_strategy,omitempty" bson:"selected_buying_strategy,omitempty"`
 | 
			
		||||
	SelectedStrategy         int                   `json:"selected_strategy,omitempty" bson:"selected_strategy,omitempty"`
 | 
			
		||||
	Partnerships             []T                   `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) ClearEnv() {
 | 
			
		||||
	ri.Env = []models.Param{}
 | 
			
		||||
	ri.Inputs = []models.Param{}
 | 
			
		||||
	ri.Outputs = []models.Param{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF {
 | 
			
		||||
	if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
 | 
			
		||||
		prts := ri.Partnerships[ri.SelectedPartnershipIndex]
 | 
			
		||||
		return prts.GetProfile(ri.SelectedBuyingStrategy, ri.SelectedBuyingStrategy)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF {
 | 
			
		||||
	if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
 | 
			
		||||
		return ri.Partnerships[ri.SelectedPartnershipIndex]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF {
 | 
			
		||||
	partners := []ResourcePartnerITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		if p.GetPeerGroups()[peerID] != nil {
 | 
			
		||||
			for _, g := range p.GetPeerGroups()[peerID] {
 | 
			
		||||
				if slices.Contains(groups, g) {
 | 
			
		||||
					partners = append(partners, p)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return partners
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	pricings := []pricing.PricingProfileITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
 | 
			
		||||
	}
 | 
			
		||||
	return pricings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) {
 | 
			
		||||
	groups := []map[string][]string{}
 | 
			
		||||
	partners := []ResourcePartnerITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		partners = append(partners, p)
 | 
			
		||||
		groups = append(groups, p.GetPeerGroups())
 | 
			
		||||
	}
 | 
			
		||||
	return partners, groups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) ClearPeerGroups() {
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		p.ClearPeerGroups()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
 | 
			
		||||
	Namespace       string              `json:"namespace" bson:"namespace" default:"default-namespace"`
 | 
			
		||||
	PeerGroups      map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"`
 | 
			
		||||
	PricingProfiles map[int]map[int]T   `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"`
 | 
			
		||||
	// to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourcePartnerShip[T]) GetProfile(buying int, strategy int) pricing.PricingProfileITF {
 | 
			
		||||
	if strat, ok := ri.PricingProfiles[buying]; ok {
 | 
			
		||||
		if profile, ok := strat[strategy]; ok {
 | 
			
		||||
			return profile
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Le pricing doit être selectionné lors d'un scheduling...
 | 
			
		||||
le type de paiement défini le type de stratégie de paiement
 | 
			
		||||
note : il faut rajouté - une notion de facturation
 | 
			
		||||
Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat.
 | 
			
		||||
Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc.
 | 
			
		||||
*/
 | 
			
		||||
func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	profiles := []pricing.PricingProfileITF{}
 | 
			
		||||
	if ri.PeerGroups[peerID] == nil {
 | 
			
		||||
		return profiles
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range ri.PeerGroups[peerID] {
 | 
			
		||||
		if slices.Contains(groups, p) || slices.Contains(groups, "*") {
 | 
			
		||||
			for _, ri := range ri.PricingProfiles {
 | 
			
		||||
				for _, i := range ri {
 | 
			
		||||
					profiles = append(profiles, i)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return profiles
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return profiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
 | 
			
		||||
	return rp.PeerGroups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
 | 
			
		||||
	rp.PeerGroups = map[string][]string{}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								models/resources/resource_accessor.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										106
									
								
								models/resources/resource_accessor.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceMongoAccessor[T ResourceInterface] struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	generateData           func() utils.DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the computeMongoAccessor
 | 
			
		||||
func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIRequest, g func() utils.DBObject) *ResourceMongoAccessor[T] {
 | 
			
		||||
	if !slices.Contains([]tools.DataType{
 | 
			
		||||
		tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE,
 | 
			
		||||
		tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE,
 | 
			
		||||
		tools.DATA_RESOURCE,
 | 
			
		||||
	}, t) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &ResourceMongoAccessor[T]{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
		generateData: g,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't update a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	set.(T).Trim()
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, dca, dca.generateData())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't create a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	data.(T).Trim()
 | 
			
		||||
	return utils.GenericStoreOne(data, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't copy/publish a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	return dca.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		d.(T).SetAllowedInstances(dca.Request)
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	if filters == nil && search == "*" {
 | 
			
		||||
		return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
	}
 | 
			
		||||
	return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *ResourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters {
 | 
			
		||||
	return &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.abstractobject.name":       {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.type":                      {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.short_description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.description":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.owners.name":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										208
									
								
								models/resources/storage.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										208
									
								
								models/resources/storage.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* StorageResource is a struct that represents a storage resource
 | 
			
		||||
* it defines the resource storage
 | 
			
		||||
 */
 | 
			
		||||
type StorageResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*StorageResourceInstance]                  // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	StorageType                                            enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // Type is the type of the storage
 | 
			
		||||
	Acronym                                                string           `bson:"acronym,omitempty" json:"acronym,omitempty"`    // Acronym is the acronym of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *StorageResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*StorageResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *StorageResource) GetType() string {
 | 
			
		||||
	return tools.STORAGE_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.STORAGE_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedStorageResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourceInstance struct {
 | 
			
		||||
	ResourceInstance[*StorageResourcePartnership]
 | 
			
		||||
	Source        string           `bson:"source,omitempty" json:"source,omitempty"` // Source is the source of the storage
 | 
			
		||||
	Path          string           `bson:"path,omitempty" json:"path,omitempty"`     // Path is the store folders in the source
 | 
			
		||||
	Local         bool             `bson:"local" json:"local"`
 | 
			
		||||
	SecurityLevel string           `bson:"security_level,omitempty" json:"security_level,omitempty"`
 | 
			
		||||
	SizeType      enum.StorageSize `bson:"size_type" json:"size_type" default:"0"`           // SizeType is the type of the storage size
 | 
			
		||||
	SizeGB        int64            `bson:"size,omitempty" json:"size,omitempty"`             // Size is the size of the storage
 | 
			
		||||
	Encryption    bool             `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
 | 
			
		||||
	Redundancy    string           `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
 | 
			
		||||
	Throughput    string           `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *StorageResourceInstance) ClearEnv() {
 | 
			
		||||
	ri.Env = []models.Param{}
 | 
			
		||||
	ri.Inputs = []models.Param{}
 | 
			
		||||
	ri.Outputs = []models.Param{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *StorageResourceInstance) StoreDraftDefault() {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, p := range ri.ResourceInstance.Env {
 | 
			
		||||
		if p.Attr == "source" {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		ri.ResourceInstance.Env = append(ri.ResourceInstance.Env, models.Param{
 | 
			
		||||
			Attr:     "source",
 | 
			
		||||
			Value:    ri.Source,
 | 
			
		||||
			Readonly: true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	ri.ResourceInstance.StoreDraftDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*StorageResourcePricingProfile]
 | 
			
		||||
	MaxSizeGBAllowed     float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
 | 
			
		||||
	OnlyEncryptedAllowed bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PrivilegeStoragePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	BASIC_STORAGE PrivilegeStoragePricingStrategy = iota
 | 
			
		||||
	GARANTED_ON_DELAY_STORAGE
 | 
			
		||||
	GARANTED_STORAGE
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PrivilegeStoragePricingStrategyList() []PrivilegeStoragePricingStrategy {
 | 
			
		||||
	return []PrivilegeStoragePricingStrategy{BASIC_STORAGE, GARANTED_ON_DELAY_STORAGE, GARANTED_STORAGE}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t PrivilegeStoragePricingStrategy) String() string {
 | 
			
		||||
	return [...]string{"NO MEMORY HOLDING", "KEEPED ON MEMORY GARANTED DURING DELAY", "KEEPED ON MEMORY GARANTED"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PER_DATA_STORED StorageResourcePricingStrategy = iota + 6
 | 
			
		||||
	PER_TB_STORED
 | 
			
		||||
	PER_GB_STORED
 | 
			
		||||
	PER_MB_STORED
 | 
			
		||||
	PER_KB_STORED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
 | 
			
		||||
	return []StorageResourcePricingStrategy{PER_DATA_STORED, PER_TB_STORED, PER_GB_STORED, PER_MB_STORED, PER_KB_STORED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) String() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetStrategy() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToStorageResourcePricingStrategy(i int) StorageResourcePricingStrategy {
 | 
			
		||||
	return StorageResourcePricingStrategy(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PER_DATA_STORED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_TB_STORED:
 | 
			
		||||
		return amountOfDataGB * 1000, nil
 | 
			
		||||
	case PER_GB_STORED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_MB_STORED:
 | 
			
		||||
		return (amountOfDataGB * 1000), nil
 | 
			
		||||
	case PER_KB_STORED:
 | 
			
		||||
		return amountOfDataGB * 1000000, nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePricingProfile struct {
 | 
			
		||||
	pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	if p.Pricing.BuyingStrategy == pricing.PERMANENT {
 | 
			
		||||
		p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, val, start, &end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedStorageResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedStorageResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.STORAGE_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedStorageResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	var err error
 | 
			
		||||
	amountOfData := float64(1)
 | 
			
		||||
	if pricing.GetOverrideStrategyValue() >= 0 {
 | 
			
		||||
		amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								models/resources/tests/compute_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								models/resources/tests/compute_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &resources.ComputeResource{}
 | 
			
		||||
	assert.Equal(t, tools.COMPUTE_RESOURCE.String(), r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	cr := &resources.ComputeResource{}
 | 
			
		||||
	accessor := cr.GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	cr := &resources.ComputeResource{}
 | 
			
		||||
	cr.UUID = "comp123"
 | 
			
		||||
	cr.AbstractInstanciatedResource.UUID = cr.UUID
 | 
			
		||||
	result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req)
 | 
			
		||||
	assert.NotNil(t, result)
 | 
			
		||||
	assert.IsType(t, &resources.PricedComputeResource{}, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	profile := resources.ComputeResourcePricingProfile{
 | 
			
		||||
		CPUsPrices: map[string]float64{"Xeon": 2.0},
 | 
			
		||||
		ExploitPricingProfile: pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
			AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
				Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{Price: 1.0},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := profile.GetPrice(2, 3600, start, end, "cpus", "Xeon")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, price, float64(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) {
 | 
			
		||||
	profile := resources.ComputeResourcePricingProfile{}
 | 
			
		||||
	_, err := profile.GetPrice(1, 3600, time.Now(), time.Now())
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "params must be set", err.Error())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	r := resources.PricedComputeResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID:               "comp456",
 | 
			
		||||
			UsageStart:               &start,
 | 
			
		||||
			UsageEnd:                 &end,
 | 
			
		||||
			ExplicitBookingDurationS: 3600,
 | 
			
		||||
		},
 | 
			
		||||
		CPUsLocated: map[string]float64{"Xeon": 2},
 | 
			
		||||
		GPUsLocated: map[string]float64{"Tesla": 1},
 | 
			
		||||
		RAMLocated:  4,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := r.GetPrice()
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, price, float64(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_GetPrice_MissingProfile(t *testing.T) {
 | 
			
		||||
	r := resources.PricedComputeResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "comp789",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := r.GetPrice()
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_FillWithDefaultProcessingUsage(t *testing.T) {
 | 
			
		||||
	usage := &resources.ProcessingUsage{
 | 
			
		||||
		CPUs: map[string]*models.CPU{"t": {Model: "Xeon", Cores: 4}},
 | 
			
		||||
		GPUs: map[string]*models.GPU{"t1": {Model: "Tesla"}},
 | 
			
		||||
		RAM:  &models.RAM{SizeGb: 16},
 | 
			
		||||
	}
 | 
			
		||||
	r := &resources.PricedComputeResource{
 | 
			
		||||
		CPUsLocated: make(map[string]float64),
 | 
			
		||||
		GPUsLocated: make(map[string]float64),
 | 
			
		||||
		RAMLocated:  0,
 | 
			
		||||
	}
 | 
			
		||||
	r.FillWithDefaultProcessingUsage(usage)
 | 
			
		||||
	assert.Equal(t, float64(4), r.CPUsLocated["Xeon"])
 | 
			
		||||
	assert.Equal(t, float64(1), r.GPUsLocated["Tesla"])
 | 
			
		||||
	assert.Equal(t, float64(16), r.RAMLocated)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								models/resources/tests/data_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								models/resources/tests/data_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDataResource_GetType(t *testing.T) {
 | 
			
		||||
	d := &resources.DataResource{}
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE.String(), d.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	acc := (&resources.DataResource{}).GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	d := &resources.DataResource{}
 | 
			
		||||
	d.UUID = "123"
 | 
			
		||||
	res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.IsType(t, &resources.PricedDataResource{}, res)
 | 
			
		||||
 | 
			
		||||
	nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.Nil(t, nilRes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataInstance_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	di := &resources.DataInstance{
 | 
			
		||||
		Source: "test-src",
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.DataResourcePartnership]{
 | 
			
		||||
			Env: []models.Param{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	di.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, di.ResourceInstance.Env, 1)
 | 
			
		||||
	assert.Equal(t, "source", di.ResourceInstance.Env[0].Attr)
 | 
			
		||||
	assert.Equal(t, "test-src", di.ResourceInstance.Env[0].Value)
 | 
			
		||||
 | 
			
		||||
	// Call again, should not duplicate
 | 
			
		||||
	di.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, di.ResourceInstance.Env, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResourcePricingStrategy_GetQuantity(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		strategy resources.DataResourcePricingStrategy
 | 
			
		||||
		input    float64
 | 
			
		||||
		expected float64
 | 
			
		||||
	}{
 | 
			
		||||
		{resources.PER_DOWNLOAD, 1, 1},
 | 
			
		||||
		{resources.PER_TB_DOWNLOADED, 1, 1000},
 | 
			
		||||
		{resources.PER_GB_DOWNLOADED, 2.5, 2.5},
 | 
			
		||||
		{resources.PER_MB_DOWNLOADED, 1, 0.001},
 | 
			
		||||
		{resources.PER_KB_DOWNLOADED, 1, 0.000001},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		q, err := tt.strategy.GetQuantity(tt.input)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.InDelta(t, tt.expected, q, 1e-9)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := resources.DataResourcePricingStrategy(999).GetQuantity(1)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResourcePricingProfile_IsPurchased(t *testing.T) {
 | 
			
		||||
	profile := &resources.DataResourcePricingProfile{}
 | 
			
		||||
	profile.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	assert.True(t, profile.IsPurchasable())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetPrice(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	later := now.Add(1 * time.Hour)
 | 
			
		||||
	mockPrice := 42.0
 | 
			
		||||
 | 
			
		||||
	pricingProfile := &resources.DataResourcePricingProfile{AccessPricingProfile: pricing.AccessPricingProfile[resources.DataResourcePricingStrategy]{
 | 
			
		||||
		Pricing: pricing.PricingStrategy[resources.DataResourcePricingStrategy]{Price: 42.0}},
 | 
			
		||||
	}
 | 
			
		||||
	pricingProfile.Pricing.OverrideStrategy = resources.PER_GB_DOWNLOADED
 | 
			
		||||
 | 
			
		||||
	r := &resources.PricedDataResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			UsageStart: &now,
 | 
			
		||||
			UsageEnd:   &later,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := r.GetPrice()
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, mockPrice, price)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetPrice_NoProfiles(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedDataResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "test-resource",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := r.GetPrice()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedDataResource{}
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								models/resources/tests/priced_resource_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								models/resources/tests/priced_resource_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ---- Mock PricingProfile ----
 | 
			
		||||
 | 
			
		||||
type MockPricingProfile struct {
 | 
			
		||||
	pricing.PricingProfileITF
 | 
			
		||||
	Purchased  bool
 | 
			
		||||
	ReturnErr  bool
 | 
			
		||||
	ReturnCost float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return m.Purchased
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPricingProfile) GetPrice(amount float64, explicitDuration float64, start time.Time, end time.Time, _ ...string) (float64, error) {
 | 
			
		||||
	if m.ReturnErr {
 | 
			
		||||
		return 0, errors.New("mock error")
 | 
			
		||||
	}
 | 
			
		||||
	return m.ReturnCost, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---- Tests ----
 | 
			
		||||
 | 
			
		||||
func TestGetIDAndCreatorAndType(t *testing.T) {
 | 
			
		||||
	r := resources.PricedResource{
 | 
			
		||||
		ResourceID:   "res-123",
 | 
			
		||||
		CreatorID:    "user-abc",
 | 
			
		||||
		ResourceType: tools.DATA_RESOURCE,
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(t, "res-123", r.GetID())
 | 
			
		||||
	assert.Equal(t, "user-abc", r.GetCreatorID())
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsPurchased(t *testing.T) {
 | 
			
		||||
	t.Run("nil selected pricing returns false", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{}
 | 
			
		||||
		assert.False(t, r.IsPurchasable())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("returns true if pricing profile is purchased", func(t *testing.T) {
 | 
			
		||||
		mock := &MockPricingProfile{Purchased: true}
 | 
			
		||||
		r := &resources.PricedResource{SelectedPricing: mock}
 | 
			
		||||
		assert.True(t, r.IsPurchasable())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAndSetLocationStartEnd(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedResource{}
 | 
			
		||||
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	r.SetLocationStart(now)
 | 
			
		||||
	r.SetLocationEnd(now.Add(2 * time.Hour))
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, now, *r.GetLocationStart())
 | 
			
		||||
	assert.Equal(t, now.Add(2*time.Hour), *r.GetLocationEnd())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetExplicitDurationInS(t *testing.T) {
 | 
			
		||||
	t.Run("uses explicit duration if set", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{ExplicitBookingDurationS: 3600}
 | 
			
		||||
		assert.Equal(t, 3600.0, r.GetExplicitDurationInS())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("computes duration from start and end", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(2 * time.Hour)
 | 
			
		||||
		r := &resources.PricedResource{UsageStart: &start, UsageEnd: &end}
 | 
			
		||||
		assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("defaults to 1 hour when times not set", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{}
 | 
			
		||||
		assert.InDelta(t, 3600.0, r.GetExplicitDurationInS(), 0.1)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetPrice(t *testing.T) {
 | 
			
		||||
	t.Run("returns error if no pricing profile", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{ResourceID: "no-profile"}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
		assert.Equal(t, 0.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("uses first profile if selected is nil", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(30 * time.Minute)
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			UsageStart: &start,
 | 
			
		||||
			UsageEnd:   &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, 42.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("returns error if profile GetPrice fails", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(1 * time.Hour)
 | 
			
		||||
		mock := &MockPricingProfile{ReturnErr: true}
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			SelectedPricing: mock,
 | 
			
		||||
			UsageStart:      &start,
 | 
			
		||||
			UsageEnd:        &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		assert.Equal(t, 0.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("uses SelectedPricing if set", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(1 * time.Hour)
 | 
			
		||||
		mock := &MockPricingProfile{ReturnCost: 10.0}
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			SelectedPricing: mock,
 | 
			
		||||
			UsageStart:      &start,
 | 
			
		||||
			UsageEnd:        &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, 10.0, price)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								models/resources/tests/processing_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								models/resources/tests/processing_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	. "cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestProcessingResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &ProcessingResource{}
 | 
			
		||||
	assert.Equal(t, tools.PROCESSING_RESOURCE.String(), r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedProcessingResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &PricedProcessingResource{}
 | 
			
		||||
	assert.Equal(t, tools.PROCESSING_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	after := now.Add(2 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		input    PricedProcessingResource
 | 
			
		||||
		expected float64
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Service without explicit duration",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				IsService: true,
 | 
			
		||||
			},
 | 
			
		||||
			expected: -1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Nil start time, non-service",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					UsageStart: nil,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: float64((1 * time.Hour).Seconds()),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Duration computed from start and end",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					UsageStart: &now,
 | 
			
		||||
					UsageEnd:   &after,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: float64((2 * time.Hour).Seconds()),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Explicit duration takes precedence",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					ExplicitBookingDurationS: 1337,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: 1337,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			assert.Equal(t, test.expected, test.input.GetExplicitDurationInS())
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	request := &tools.APIRequest{}
 | 
			
		||||
	r := &ProcessingResource{}
 | 
			
		||||
	acc := r.GetAccessor(request)
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
	mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
		Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
 | 
			
		||||
			Price: 100.0,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	profile := &ProcessingResourcePricingProfile{mockPricing}
 | 
			
		||||
	price, err := profile.GetPrice(0, 0, start, end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 100.0, price)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResourcePricingProfile_IsPurchased(t *testing.T) {
 | 
			
		||||
	purchased := &ProcessingResourcePricingProfile{
 | 
			
		||||
		AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
			Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
 | 
			
		||||
				BuyingStrategy: pricing.PERMANENT,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	assert.True(t, purchased.IsPurchasable())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								models/resources/tests/resource_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								models/resources/tests/resource_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockInstance struct {
 | 
			
		||||
	ID   string
 | 
			
		||||
	Name string
 | 
			
		||||
	resources.ResourceInstance[*MockPartner]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockInstance) GetID() string    { return m.ID }
 | 
			
		||||
func (m *MockInstance) GetName() string  { return m.Name }
 | 
			
		||||
func (m *MockInstance) ClearEnv()        {}
 | 
			
		||||
func (m *MockInstance) ClearPeerGroups() {}
 | 
			
		||||
func (m *MockInstance) GetProfile() pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockInstance) GetPeerGroups() ([]resources.ResourcePartnerITF, []map[string][]string) {
 | 
			
		||||
	return nil, []map[string][]string{
 | 
			
		||||
		{"peer1": {"group1"}},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockPartner struct {
 | 
			
		||||
	groups map[string][]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPartner) GetProfile(buying int, strategy int) pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPartner) GetPeerGroups() map[string][]string {
 | 
			
		||||
	return m.groups
 | 
			
		||||
}
 | 
			
		||||
func (m *MockPartner) ClearPeerGroups() {}
 | 
			
		||||
func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockDBObject struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	isDraft bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockDBObject) IsDrafted() bool {
 | 
			
		||||
	return m.isDraft
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSelectedInstance_WithValidIndex(t *testing.T) {
 | 
			
		||||
	index := 1
 | 
			
		||||
	inst1 := &MockInstance{ID: "1"}
 | 
			
		||||
	inst2 := &MockInstance{ID: "2"}
 | 
			
		||||
	resource := &resources.AbstractInstanciatedResource[*MockInstance]{
 | 
			
		||||
		AbstractResource: resources.AbstractResource{SelectedInstanceIndex: &index},
 | 
			
		||||
		Instances:        []*MockInstance{inst1, inst2},
 | 
			
		||||
	}
 | 
			
		||||
	result := resource.GetSelectedInstance()
 | 
			
		||||
	assert.Equal(t, inst2, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSelectedInstance_NoIndex(t *testing.T) {
 | 
			
		||||
	inst := &MockInstance{ID: "1"}
 | 
			
		||||
	resource := &resources.AbstractInstanciatedResource[*MockInstance]{
 | 
			
		||||
		Instances: []*MockInstance{inst},
 | 
			
		||||
	}
 | 
			
		||||
	result := resource.GetSelectedInstance()
 | 
			
		||||
	assert.Equal(t, inst, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate_WhenOnlyStateDiffers(t *testing.T) {
 | 
			
		||||
	resource := &resources.AbstractResource{AbstractObject: utils.AbstractObject{IsDraft: false}}
 | 
			
		||||
	set := &MockDBObject{isDraft: true}
 | 
			
		||||
	canUpdate, updated := resource.CanUpdate(set)
 | 
			
		||||
	assert.True(t, canUpdate)
 | 
			
		||||
	assert.Equal(t, set, updated)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAuthAction_WithMatchingGroup(t *testing.T) {
 | 
			
		||||
	inst := &MockInstance{
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*MockPartner]{
 | 
			
		||||
			Partnerships: []*MockPartner{
 | 
			
		||||
				{groups: map[string][]string{"peer1": {"group1"}}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer1", Groups: []string{"group1"}}
 | 
			
		||||
	result := resources.VerifyAuthAction([]*MockInstance{inst}, req)
 | 
			
		||||
	assert.Len(t, result, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FakeResource struct {
 | 
			
		||||
	resources.AbstractInstanciatedResource[*MockInstance]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FakeResource) Trim()                                 {}
 | 
			
		||||
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {}
 | 
			
		||||
func (f *FakeResource) VerifyAuth(*tools.APIRequest) bool     { return true }
 | 
			
		||||
 | 
			
		||||
func TestNewAccessor_ReturnsValid(t *testing.T) {
 | 
			
		||||
	acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject {
 | 
			
		||||
		return &FakeResource{}
 | 
			
		||||
	})
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								models/resources/tests/storage_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								models/resources/tests/storage_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_GetType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	assert.Equal(t, tools.STORAGE_RESOURCE.String(), res.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	accessor := res.GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_ConvertToPricedResource_ValidType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	res.AbstractInstanciatedResource.CreatorID = "creator"
 | 
			
		||||
	res.AbstractInstanciatedResource.UUID = "res-id"
 | 
			
		||||
	priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.NotNil(t, priced)
 | 
			
		||||
	assert.IsType(t, &resources.PricedStorageResource{}, priced)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.Nil(t, priced)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourceInstance_ClearEnv(t *testing.T) {
 | 
			
		||||
	inst := &resources.StorageResourceInstance{
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.StorageResourcePartnership]{
 | 
			
		||||
			Env:     []models.Param{{Attr: "A"}},
 | 
			
		||||
			Inputs:  []models.Param{{Attr: "B"}},
 | 
			
		||||
			Outputs: []models.Param{{Attr: "C"}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inst.ClearEnv()
 | 
			
		||||
	assert.Empty(t, inst.Env)
 | 
			
		||||
	assert.Empty(t, inst.Inputs)
 | 
			
		||||
	assert.Empty(t, inst.Outputs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourceInstance_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	inst := &resources.StorageResourceInstance{
 | 
			
		||||
		Source: "my-source",
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.StorageResourcePartnership]{
 | 
			
		||||
			Env: []models.Param{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inst.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, inst.Env, 1)
 | 
			
		||||
	assert.Equal(t, "source", inst.Env[0].Attr)
 | 
			
		||||
	assert.Equal(t, "my-source", inst.Env[0].Value)
 | 
			
		||||
	assert.True(t, inst.Env[0].Readonly)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourcePricingStrategy_GetQuantity(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		strategy resources.StorageResourcePricingStrategy
 | 
			
		||||
		dataGB   float64
 | 
			
		||||
		expect   float64
 | 
			
		||||
	}{
 | 
			
		||||
		{resources.PER_DATA_STORED, 1.2, 1.2},
 | 
			
		||||
		{resources.PER_TB_STORED, 1.2, 1200},
 | 
			
		||||
		{resources.PER_GB_STORED, 2.5, 2.5},
 | 
			
		||||
		{resources.PER_MB_STORED, 1.0, 1000},
 | 
			
		||||
		{resources.PER_KB_STORED, 0.1, 100000},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		q, err := tt.strategy.GetQuantity(tt.dataGB)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, tt.expect, q)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourcePricingStrategy_GetQuantity_Invalid(t *testing.T) {
 | 
			
		||||
	invalid := resources.StorageResourcePricingStrategy(99)
 | 
			
		||||
	q, err := invalid.GetQuantity(1.0)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 0.0, q)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) {
 | 
			
		||||
	res := &resources.PricedStorageResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "res-id",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := res.GetPrice()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								models/resources/tests/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/resources/tests/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_GetType(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	assert.Equal(t, tools.WORKFLOW_RESOURCE.String(), w.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{
 | 
			
		||||
		AbstractResource: resources.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{
 | 
			
		||||
				Name:      "Test Workflow",
 | 
			
		||||
				UUID:      "workflow-uuid",
 | 
			
		||||
				CreatorID: "creator-id",
 | 
			
		||||
			},
 | 
			
		||||
			Logo: "logo.png",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{
 | 
			
		||||
		PeerID: "peer-1",
 | 
			
		||||
		Groups: []string{"group1"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req)
 | 
			
		||||
	assert.Equal(t, "creator-id", pr.GetCreatorID())
 | 
			
		||||
	assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_ClearEnv(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	assert.Equal(t, w, w.ClearEnv())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_Trim(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	w.Trim()
 | 
			
		||||
	// nothing to assert; just test that it doesn't panic
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_SetAllowedInstances(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	w.SetAllowedInstances(&tools.APIRequest{})
 | 
			
		||||
	// no-op; just confirm no crash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	request := &tools.APIRequest{}
 | 
			
		||||
	accessor := w.GetAccessor(request)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								models/resources/workflow.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								models/resources/workflow.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WorkflowResourcePricingProfile struct{}
 | 
			
		||||
 | 
			
		||||
// WorkflowResource is a struct that represents a workflow resource
 | 
			
		||||
// it defines the resource workflow
 | 
			
		||||
type WorkflowResource struct {
 | 
			
		||||
	AbstractResource
 | 
			
		||||
	WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowResource) GetType() string {
 | 
			
		||||
	return tools.WORKFLOW_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) ClearEnv() utils.DBObject {
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) Trim() {
 | 
			
		||||
	/* EMPTY */
 | 
			
		||||
}
 | 
			
		||||
func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
 | 
			
		||||
	/* EMPTY */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	return &PricedResource{
 | 
			
		||||
		Name:         w.Name,
 | 
			
		||||
		Logo:         w.Logo,
 | 
			
		||||
		ResourceID:   w.UUID,
 | 
			
		||||
		ResourceType: t,
 | 
			
		||||
		CreatorID:    w.CreatorID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/tests/models_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								models/tests/models_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestModel_ReturnsValidInstances(t *testing.T) {
 | 
			
		||||
	for name, _ := range models.ModelsCatalog {
 | 
			
		||||
		t.Run(name, func(t *testing.T) {
 | 
			
		||||
			modelInt, _ := strconv.Atoi(name)
 | 
			
		||||
			obj := models.Model(modelInt)
 | 
			
		||||
			assert.NotNil(t, obj, "Model() returned nil for valid model name %s", name)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestModel_UnknownModelReturnsNil(t *testing.T) {
 | 
			
		||||
	invalidModelInt := -9999 // unlikely to be valid
 | 
			
		||||
	obj := models.Model(invalidModelInt)
 | 
			
		||||
	assert.Nil(t, obj)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetModelsNames_ReturnsAllKeys(t *testing.T) {
 | 
			
		||||
	names := models.GetModelsNames()
 | 
			
		||||
	assert.Len(t, names, len(models.ModelsCatalog))
 | 
			
		||||
 | 
			
		||||
	seen := make(map[string]bool)
 | 
			
		||||
	for _, name := range names {
 | 
			
		||||
		seen[name] = true
 | 
			
		||||
	}
 | 
			
		||||
	for key := range models.ModelsCatalog {
 | 
			
		||||
		assert.Contains(t, seen, key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								models/utils/abstracts.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										176
									
								
								models/utils/abstracts.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// single instance of the validator used in every model Struct to validate the fields
 | 
			
		||||
var validate = validator.New(validator.WithRequiredStructEnabled())
 | 
			
		||||
 | 
			
		||||
type AccessMode int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Private AccessMode = iota
 | 
			
		||||
	Public
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* AbstractObject is a struct that represents the basic fields of an object
 | 
			
		||||
* it defines the object id and name
 | 
			
		||||
* every data in base root model should inherit from this struct (only exception is the ResourceModel)
 | 
			
		||||
 */
 | 
			
		||||
type AbstractObject struct {
 | 
			
		||||
	UUID          string     `json:"id,omitempty" bson:"id,omitempty" validate:"required"`
 | 
			
		||||
	Name          string     `json:"name,omitempty" bson:"name,omitempty" validate:"required"`
 | 
			
		||||
	IsDraft       bool       `json:"is_draft" bson:"is_draft" default:"false"`
 | 
			
		||||
	CreatorID     string     `json:"creator_id,omitempty" bson:"creator_id,omitempty"`
 | 
			
		||||
	UserCreatorID string     `json:"user_creator_id,omitempty" bson:"user_creator_id,omitempty"`
 | 
			
		||||
	CreationDate  time.Time  `json:"creation_date,omitempty" bson:"creation_date,omitempty"`
 | 
			
		||||
	UpdateDate    time.Time  `json:"update_date,omitempty" bson:"update_date,omitempty"`
 | 
			
		||||
	UpdaterID     string     `json:"updater_id,omitempty" bson:"updater_id,omitempty"`
 | 
			
		||||
	UserUpdaterID string     `json:"user_updater_id,omitempty" bson:"user_updater_id,omitempty"`
 | 
			
		||||
	AccessMode    AccessMode `json:"access_mode" bson:"access_mode" default:"0"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *AbstractObject) GetAccessor(request *tools.APIRequest) Accessor {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) SetID(id string) {
 | 
			
		||||
	r.UUID = id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) GenerateID() {
 | 
			
		||||
	if r.UUID == "" {
 | 
			
		||||
		r.UUID = uuid.New().String()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) CanUpdate(set DBObject) (bool, DBObject) {
 | 
			
		||||
	return true, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) CanDelete() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) IsDrafted() bool {
 | 
			
		||||
	return r.IsDraft
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetID implements ShallowDBObject.
 | 
			
		||||
func (ao AbstractObject) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetName implements ShallowDBObject.
 | 
			
		||||
func (ao AbstractObject) GetName() string {
 | 
			
		||||
	return ao.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) GetCreatorID() string {
 | 
			
		||||
	return ao.CreatorID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) UpToDate(user string, peer string, create bool) {
 | 
			
		||||
	ao.UpdateDate = time.Now()
 | 
			
		||||
	ao.UpdaterID = peer
 | 
			
		||||
	ao.UserUpdaterID = user
 | 
			
		||||
	if create {
 | 
			
		||||
		ao.CreationDate = time.Now()
 | 
			
		||||
		ao.CreatorID = peer
 | 
			
		||||
		ao.UserCreatorID = user
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters {
 | 
			
		||||
	if search == "*" {
 | 
			
		||||
		search = ""
 | 
			
		||||
	}
 | 
			
		||||
	return &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
 | 
			
		||||
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
		}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Deserialize(j map[string]interface{}, obj DBObject) DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, obj)
 | 
			
		||||
	return obj
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Serialize(obj DBObject) map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(obj)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AbstractAccessor struct {
 | 
			
		||||
	Logger                zerolog.Logger    // Logger is the logger of the accessor, it's a specilized logger for the accessor
 | 
			
		||||
	Type                  tools.DataType    // Type is the data type of the accessor
 | 
			
		||||
	Request               *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection
 | 
			
		||||
	ResourceModelAccessor Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractAccessor) GetRequest() *tools.APIRequest {
 | 
			
		||||
	return r.Request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetUser() string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.Username
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetPeerID() string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.PeerID
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetGroups() []string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.Groups
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetLogger() *zerolog.Logger {
 | 
			
		||||
	return &dma.Logger
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetType() tools.DataType {
 | 
			
		||||
	return dma.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.Caller
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										166
									
								
								models/utils/common.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										166
									
								
								models/utils/common.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	mgb "go.mongodb.org/mongo-driver/mongo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Owner struct {
 | 
			
		||||
	Name string `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func VerifyAccess(a Accessor, id string) error {
 | 
			
		||||
	data, _, err := a.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	data.GenerateID()
 | 
			
		||||
	data.StoreDraftDefault()
 | 
			
		||||
	data.UpToDate(a.GetUser(), a.GetPeerID(), true)
 | 
			
		||||
	f := dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{
 | 
			
		||||
			"abstractresource.abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
			"abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access : " + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 {
 | 
			
		||||
		return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists")
 | 
			
		||||
	}
 | 
			
		||||
	err := validate.Struct(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 422, err
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	res, code, err := a.LoadOne(id)
 | 
			
		||||
	if !res.CanDelete() {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !res.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access " + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	_, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
 | 
			
		||||
func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) {
 | 
			
		||||
	r, c, err := a.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, c, err
 | 
			
		||||
	}
 | 
			
		||||
	ok, newSet := r.CanUpdate(set)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	set = newSet
 | 
			
		||||
	r.UpToDate(a.GetUser(), a.GetPeerID(), false)
 | 
			
		||||
	if a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	change := set.Serialize(set) // get the changes
 | 
			
		||||
	loaded := r.Serialize(r)     // get the loaded object
 | 
			
		||||
 | 
			
		||||
	for k, v := range change { // apply the changes, with a flatten method
 | 
			
		||||
		loaded[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) {
 | 
			
		||||
	var data T
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&data)
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	return f(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft bool, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []ShallowDBObject{}
 | 
			
		||||
	var results []T
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = res.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if (a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, f(r))
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String())
 | 
			
		||||
	return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters,
 | 
			
		||||
	f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	if filters == nil && search != "" {
 | 
			
		||||
		filters = defaultFilters
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String())
 | 
			
		||||
	return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
 | 
			
		||||
func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								models/utils/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										53
									
								
								models/utils/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ShallowDBObject is an interface that defines the basic methods shallowed version of a DBObject
 | 
			
		||||
type ShallowDBObject interface {
 | 
			
		||||
	GenerateID()
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	Serialize(obj DBObject) map[string]interface{}
 | 
			
		||||
	Deserialize(j map[string]interface{}, obj DBObject) DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DBObject is an interface that defines the basic methods for a DBObject
 | 
			
		||||
type DBObject interface {
 | 
			
		||||
	GenerateID()
 | 
			
		||||
	SetID(id string)
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	IsDrafted() bool
 | 
			
		||||
	CanDelete() bool
 | 
			
		||||
	StoreDraftDefault()
 | 
			
		||||
	GetCreatorID() string
 | 
			
		||||
	UpToDate(user string, peer string, create bool)
 | 
			
		||||
	CanUpdate(set DBObject) (bool, DBObject)
 | 
			
		||||
	VerifyAuth(request *tools.APIRequest) bool
 | 
			
		||||
	Serialize(obj DBObject) map[string]interface{}
 | 
			
		||||
	GetAccessor(request *tools.APIRequest) Accessor
 | 
			
		||||
	Deserialize(j map[string]interface{}, obj DBObject) DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Accessor is an interface that defines the basic methods for an Accessor
 | 
			
		||||
type Accessor interface {
 | 
			
		||||
	GetUser() string
 | 
			
		||||
	GetPeerID() string
 | 
			
		||||
	GetGroups() []string
 | 
			
		||||
	ShouldVerifyAuth() bool
 | 
			
		||||
	GetType() tools.DataType
 | 
			
		||||
	GetLogger() *zerolog.Logger
 | 
			
		||||
	GetCaller() *tools.HTTPCaller
 | 
			
		||||
	GetRequest() *tools.APIRequest
 | 
			
		||||
	LoadOne(id string) (DBObject, int, error)
 | 
			
		||||
	DeleteOne(id string) (DBObject, int, error)
 | 
			
		||||
	CopyOne(data DBObject) (DBObject, int, error)
 | 
			
		||||
	StoreOne(data DBObject) (DBObject, int, error)
 | 
			
		||||
	LoadAll(isDraft bool) ([]ShallowDBObject, int, error)
 | 
			
		||||
	UpdateOne(set DBObject, id string) (DBObject, int, error)
 | 
			
		||||
	Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								models/utils/tests/abstracts_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								models/utils/tests/abstracts_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
package models_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGenerateID(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	ao.GenerateID()
 | 
			
		||||
	assert.NotEmpty(t, ao.UUID)
 | 
			
		||||
	_, err := uuid.Parse(ao.UUID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreDraftDefault(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{IsDraft: true}
 | 
			
		||||
	ao.StoreDraftDefault()
 | 
			
		||||
	assert.False(t, ao.IsDraft)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	res, set := ao.CanUpdate(nil)
 | 
			
		||||
	assert.True(t, res)
 | 
			
		||||
	assert.Nil(t, set)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	assert.True(t, ao.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsDrafted(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{IsDraft: true}
 | 
			
		||||
	assert.True(t, ao.IsDrafted())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetID(t *testing.T) {
 | 
			
		||||
	u := uuid.New().String()
 | 
			
		||||
	ao := &utils.AbstractObject{UUID: u}
 | 
			
		||||
	assert.Equal(t, u, ao.GetID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetName(t *testing.T) {
 | 
			
		||||
	name := "MyObject"
 | 
			
		||||
	ao := &utils.AbstractObject{Name: name}
 | 
			
		||||
	assert.Equal(t, name, ao.GetName())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetCreatorID(t *testing.T) {
 | 
			
		||||
	id := "creator-123"
 | 
			
		||||
	ao := &utils.AbstractObject{CreatorID: id}
 | 
			
		||||
	assert.Equal(t, id, ao.GetCreatorID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpToDate_CreateFalse(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	time.Sleep(time.Millisecond) // ensure time difference
 | 
			
		||||
	ao.UpToDate("user123", "peer456", false)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.UpdaterID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserUpdaterID)
 | 
			
		||||
	assert.True(t, ao.CreationDate.IsZero())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpToDate_CreateTrue(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	time.Sleep(time.Millisecond)
 | 
			
		||||
	ao.UpToDate("user123", "peer456", true)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.CreationDate, time.Second)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.UpdaterID)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.CreatorID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserUpdaterID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserCreatorID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAuth(t *testing.T) {
 | 
			
		||||
	request := &tools.APIRequest{PeerID: "peer123"}
 | 
			
		||||
	ao := &utils.AbstractObject{CreatorID: "peer123"}
 | 
			
		||||
	assert.True(t, ao.VerifyAuth(request))
 | 
			
		||||
 | 
			
		||||
	ao = &utils.AbstractObject{AccessMode: utils.Public}
 | 
			
		||||
	assert.True(t, ao.VerifyAuth(nil))
 | 
			
		||||
 | 
			
		||||
	ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
 | 
			
		||||
	request = &tools.APIRequest{PeerID: "wrong"}
 | 
			
		||||
	assert.False(t, ao.VerifyAuth(request))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetObjectFilters(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	f := ao.GetObjectFilters("*")
 | 
			
		||||
	assert.NotNil(t, f)
 | 
			
		||||
	assert.Contains(t, f.Or, "abstractobject.name")
 | 
			
		||||
	assert.Equal(t, dbs.LIKE.String(), f.Or["abstractobject.name"][0].Operator)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeserialize(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	input := map[string]interface{}{"name": "test", "id": uuid.New().String()}
 | 
			
		||||
	res := ao.Deserialize(input, &utils.AbstractObject{})
 | 
			
		||||
	assert.NotNil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSerialize(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{Name: "test", UUID: uuid.New().String()}
 | 
			
		||||
	m := ao.Serialize(ao)
 | 
			
		||||
	assert.Equal(t, "test", m["name"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAbstractAccessorMethods(t *testing.T) {
 | 
			
		||||
	r := &utils.AbstractAccessor{Request: &tools.APIRequest{Username: "alice", PeerID: "peer1", Groups: []string{"dev"}}}
 | 
			
		||||
	assert.True(t, r.ShouldVerifyAuth())
 | 
			
		||||
	assert.Equal(t, "alice", r.GetUser())
 | 
			
		||||
	assert.Equal(t, "peer1", r.GetPeerID())
 | 
			
		||||
	assert.Equal(t, []string{"dev"}, r.GetGroups())
 | 
			
		||||
	assert.Equal(t, r.Request.Caller, r.GetCaller())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										168
									
								
								models/utils/tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								models/utils/tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
			
		||||
package models_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// --- Mock Definitions ---
 | 
			
		||||
 | 
			
		||||
type MockDBObject struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetLogger() *zerolog.Logger {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetGroups() []string {
 | 
			
		||||
	return []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetCaller() *tools.HTTPCaller {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockDBObject) GenerateID()        { m.Called() }
 | 
			
		||||
func (m *MockDBObject) StoreDraftDefault() { m.Called() }
 | 
			
		||||
func (m *MockDBObject) UpToDate(user, peer string, create bool) {
 | 
			
		||||
	m.Called(user, peer, create)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) VerifyAuth(req *tools.APIRequest) bool {
 | 
			
		||||
	args := m.Called(req)
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) CanDelete() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	args := m.Called(set)
 | 
			
		||||
	return args.Bool(0), args.Get(1).(utils.DBObject)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) IsDrafted() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) Serialize(obj utils.DBObject) map[string]interface{} {
 | 
			
		||||
	args := m.Called(obj)
 | 
			
		||||
	return args.Get(0).(map[string]interface{})
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) Deserialize(mdata map[string]interface{}, obj utils.DBObject) utils.DBObject {
 | 
			
		||||
	args := m.Called(mdata, obj)
 | 
			
		||||
	return args.Get(0).(utils.DBObject)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) GetID() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) GetName() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(set, id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetRequest() *tools.APIRequest {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Get(0).(*tools.APIRequest)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetType() tools.DataType {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Get(0).(tools.DataType)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) Search(filters *dbs.Filters, s string, d bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, s, d)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetUser() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetPeerID() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Test Cases ---
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_Authorized(t *testing.T) {
 | 
			
		||||
	mockObj := new(MockDBObject)
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer"}
 | 
			
		||||
	mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
 | 
			
		||||
	mockAcc.On("ShouldVerifyAuth").Return(true)
 | 
			
		||||
	mockObj.On("VerifyAuth", req).Return(true)
 | 
			
		||||
	mockAcc.On("GetRequest").Return(req)
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "123")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_Unauthorized(t *testing.T) {
 | 
			
		||||
	mockObj := new(MockDBObject)
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer"}
 | 
			
		||||
	mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
 | 
			
		||||
	mockAcc.On("ShouldVerifyAuth").Return(true)
 | 
			
		||||
	mockObj.On("VerifyAuth", req).Return(false)
 | 
			
		||||
	mockAcc.On("GetRequest").Return(req)
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "123")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "not allowed")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_LoadError(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	mockAcc.On("LoadOne", "bad-id").Return(nil, 404, errors.New("not found"))
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "bad-id")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "not found", err.Error())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								models/workflow/graph/graph.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								models/workflow/graph/graph.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Graph is a struct that represents a graph
 | 
			
		||||
type Graph struct {
 | 
			
		||||
	Partial bool                 `json:"partial" default:"false"`                             // Partial is a flag that indicates if the graph is partial
 | 
			
		||||
	Zoom    float64              `bson:"zoom" json:"zoom" default:"1"`                        // Zoom is the graphical zoom of the graph
 | 
			
		||||
	Items   map[string]GraphItem `bson:"items" json:"items" default:"{}" validate:"required"` // Items is the list of elements in the graph
 | 
			
		||||
	Links   []GraphLink          `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) Clear(id string) {
 | 
			
		||||
	realItems := map[string]GraphItem{}
 | 
			
		||||
	for k, it := range g.Items {
 | 
			
		||||
		if k == id {
 | 
			
		||||
			realinks := []GraphLink{}
 | 
			
		||||
			for _, link := range g.Links {
 | 
			
		||||
				if link.Source.ID != id && link.Destination.ID != id {
 | 
			
		||||
					realinks = append(realinks, link)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			g.Links = realinks
 | 
			
		||||
			g.Partial = true
 | 
			
		||||
		} else {
 | 
			
		||||
			realItems[k] = it
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	g.Items = realItems
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsProcessing(item GraphItem) bool {
 | 
			
		||||
	return item.Processing != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsCompute(item GraphItem) bool {
 | 
			
		||||
	return item.Compute != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsData(item GraphItem) bool {
 | 
			
		||||
	return item.Data != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsStorage(item GraphItem) bool {
 | 
			
		||||
	return item.Storage != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsWorkflow(item GraphItem) bool {
 | 
			
		||||
	return item.Workflow != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
 | 
			
		||||
	f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) {
 | 
			
		||||
	nearestStart := float64(10000000000)
 | 
			
		||||
	oneIsInfinite := false
 | 
			
		||||
	longestDuration := float64(0)
 | 
			
		||||
	for _, link := range g.Links {
 | 
			
		||||
		for _, processing := range processings {
 | 
			
		||||
			var source string                                                                                                                             // source is the source of the link
 | 
			
		||||
			if link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the destination is the processing and the source is not a compute
 | 
			
		||||
				source = link.Source.ID
 | 
			
		||||
			} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
 | 
			
		||||
				source = link.Destination.ID
 | 
			
		||||
			}
 | 
			
		||||
			priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request)
 | 
			
		||||
			if source != "" {
 | 
			
		||||
				if priced.GetLocationStart() != nil {
 | 
			
		||||
					near := float64(priced.GetLocationStart().Sub(start).Seconds())
 | 
			
		||||
					if near < nearestStart {
 | 
			
		||||
						nearestStart = near
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				if priced.GetLocationEnd() != nil {
 | 
			
		||||
					duration := float64(priced.GetLocationEnd().Sub(*priced.GetLocationStart()).Seconds())
 | 
			
		||||
					if longestDuration < duration {
 | 
			
		||||
						longestDuration = duration
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					oneIsInfinite = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if oneIsInfinite {
 | 
			
		||||
		return nearestStart, -1
 | 
			
		||||
	}
 | 
			
		||||
	return nearestStart, longestDuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
 | 
			
		||||
 */
 | 
			
		||||
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 {
 | 
			
		||||
	currents := []float64{}        // list of current time
 | 
			
		||||
	for _, link := range g.Links { // for each link
 | 
			
		||||
		var source string                                                                     // source is the source of the link
 | 
			
		||||
		if link.Destination.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the destination is the processing and the source is not a compute
 | 
			
		||||
			source = link.Source.ID
 | 
			
		||||
		} else if link.Source.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the source is the processing and the destination is not a compute
 | 
			
		||||
			source = link.Destination.ID
 | 
			
		||||
		}
 | 
			
		||||
		if source == "" { // if source is empty, continue
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		dt, r := g.GetResource(source) // get the resource of the source
 | 
			
		||||
		if r == nil {                  // if item is nil, continue
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		priced := r.ConvertToPricedResource(dt, request)
 | 
			
		||||
		current := priced.GetExplicitDurationInS() // get the explicit duration of the item
 | 
			
		||||
		if current < 0 {                           // if current is negative, its means that duration of a before could be infinite continue
 | 
			
		||||
			return current
 | 
			
		||||
		}
 | 
			
		||||
		current += g.GetAverageTimeProcessingBeforeStart(current, source, request) // get the average time before start of the source
 | 
			
		||||
		currents = append(currents, current)                                       // append the current to the currents
 | 
			
		||||
	}
 | 
			
		||||
	var max float64 // get the max time to wait dependancies to finish
 | 
			
		||||
	for _, current := range currents {
 | 
			
		||||
		if current > max {
 | 
			
		||||
			max = current
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return max
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
 | 
			
		||||
	if item, ok := g.Items[id]; ok {
 | 
			
		||||
		if item.Data != nil {
 | 
			
		||||
			return tools.DATA_RESOURCE, item.Data
 | 
			
		||||
		} else if item.Compute != nil {
 | 
			
		||||
			return tools.COMPUTE_RESOURCE, item.Compute
 | 
			
		||||
		} else if item.Workflow != nil {
 | 
			
		||||
			return tools.WORKFLOW_RESOURCE, item.Workflow
 | 
			
		||||
		} else if item.Processing != nil {
 | 
			
		||||
			return tools.PROCESSING_RESOURCE, item.Processing
 | 
			
		||||
		} else if item.Storage != nil {
 | 
			
		||||
			return tools.STORAGE_RESOURCE, item.Storage
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tools.INVALID, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/workflow/graph/item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								models/workflow/graph/item.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GraphItem is a struct that represents an item in a graph
 | 
			
		||||
type GraphItem struct {
 | 
			
		||||
	ID                      string   `bson:"id" json:"id" validate:"required"`             // ID is the unique identifier of the item
 | 
			
		||||
	Width                   float64  `bson:"width" json:"width" validate:"required"`       // Width is the graphical width of the item
 | 
			
		||||
	Height                  float64  `bson:"height" json:"height" validate:"required"`     // Height is the graphical height of the item
 | 
			
		||||
	Position                Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item
 | 
			
		||||
	*resources.ItemResource          // ItemResource is the resource of the item affected to the item
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GraphItem) GetResource() (tools.DataType, resources.ResourceInterface) {
 | 
			
		||||
	if g.Data != nil {
 | 
			
		||||
		return tools.DATA_RESOURCE, g.Data
 | 
			
		||||
	} else if g.Compute != nil {
 | 
			
		||||
		return tools.COMPUTE_RESOURCE, g.Compute
 | 
			
		||||
	} else if g.Workflow != nil {
 | 
			
		||||
		return tools.WORKFLOW_RESOURCE, g.Workflow
 | 
			
		||||
	} else if g.Processing != nil {
 | 
			
		||||
		return tools.PROCESSING_RESOURCE, g.Processing
 | 
			
		||||
	} else if g.Storage != nil {
 | 
			
		||||
		return tools.STORAGE_RESOURCE, g.Storage
 | 
			
		||||
	}
 | 
			
		||||
	return tools.INVALID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GraphItem) Clear() {
 | 
			
		||||
	g.Data = nil
 | 
			
		||||
	g.Compute = nil
 | 
			
		||||
	g.Workflow = nil
 | 
			
		||||
	g.Processing = nil
 | 
			
		||||
	g.Storage = nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								models/workflow/graph/link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								models/workflow/graph/link.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import "cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
 | 
			
		||||
type StorageProcessingGraphLink struct {
 | 
			
		||||
	Write       bool   `json:"write" bson:"write"`
 | 
			
		||||
	Source      string `json:"source" bson:"source"`
 | 
			
		||||
	Destination string `json:"destination" bson:"destination"`
 | 
			
		||||
	FileName    string `json:"filename" bson:"filename"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GraphLink is a struct that represents a link between two items in a graph
 | 
			
		||||
type GraphLink struct {
 | 
			
		||||
	Source           Position                     `bson:"source" json:"source" validate:"required"`                         // Source is the source graphical position of the link
 | 
			
		||||
	Destination      Position                     `bson:"destination" json:"destination" validate:"required"`               // Destination is the destination graphical position of the link
 | 
			
		||||
	Style            *GraphLinkStyle              `bson:"style,omitempty" json:"style,omitempty"`                           // Style is the graphical style of the link
 | 
			
		||||
	StorageLinkInfos []StorageProcessingGraphLink `bson:"storage_link_infos,omitempty" json:"storage_link_infos,omitempty"` // StorageLinkInfo is the storage link info
 | 
			
		||||
	Env              []models.Param               `json:"env" bson:"env"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tool function to check if a link is a link between a compute and a resource
 | 
			
		||||
func (l *GraphLink) IsComputeLink(g Graph) (bool, string) {
 | 
			
		||||
	if g.Items == nil {
 | 
			
		||||
		return false, ""
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := g.Items[l.Source.ID]; ok && d.Compute != nil {
 | 
			
		||||
		return true, d.Compute.UUID
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := g.Items[l.Destination.ID]; ok && d.Compute != nil {
 | 
			
		||||
		return true, d.Compute.UUID
 | 
			
		||||
	}
 | 
			
		||||
	return false, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GraphLinkStyle is a struct that represents the style of a link in a graph
 | 
			
		||||
type GraphLinkStyle struct {
 | 
			
		||||
	Color           int64    `bson:"color" json:"color"`                         // Color is the graphical color of the link (int description of a color, can be transpose as hex)
 | 
			
		||||
	Stroke          float64  `bson:"stroke" json:"stroke"`                       // Stroke is the graphical stroke of the link
 | 
			
		||||
	Tension         float64  `bson:"tension" json:"tension"`                     // Tension is the graphical tension of the link
 | 
			
		||||
	HeadRadius      float64  `bson:"head_radius" json:"head_radius"`             // graphical pin radius
 | 
			
		||||
	DashWidth       float64  `bson:"dash_width" json:"dash_width"`               // DashWidth is the graphical dash width of the link
 | 
			
		||||
	DashSpace       float64  `bson:"dash_space" json:"dash_space"`               // DashSpace is the graphical dash space of the link
 | 
			
		||||
	EndArrow        Position `bson:"end_arrow" json:"end_arrow"`                 // EndArrow is the graphical end arrow of the link
 | 
			
		||||
	StartArrow      Position `bson:"start_arrow" json:"start_arrow"`             // StartArrow is the graphical start arrow of the link
 | 
			
		||||
	ArrowStyle      int64    `bson:"arrow_style" json:"arrow_style"`             // ArrowStyle is the graphical arrow style of the link (enum foundable in UI)
 | 
			
		||||
	ArrowDirection  int64    `bson:"arrow_direction" json:"arrow_direction"`     // ArrowDirection is the graphical arrow direction of the link (enum foundable in UI)
 | 
			
		||||
	StartArrowWidth float64  `bson:"start_arrow_width" json:"start_arrow_width"` // StartArrowWidth is the graphical start arrow width of the link
 | 
			
		||||
	EndArrowWidth   float64  `bson:"end_arrow_width" json:"end_arrow_width"`     // EndArrowWidth is the graphical end arrow width of the link
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Position is a struct that represents a graphical position
 | 
			
		||||
type Position struct {
 | 
			
		||||
	ID string  `json:"id" bson:"id"`                   // ID reprents ItemID (optionnal)
 | 
			
		||||
	X  float64 `json:"x" bson:"x" validate:"required"` // X is the graphical x position
 | 
			
		||||
	Y  float64 `json:"y" bson:"y" validate:"required"` // Y is the graphical y position
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										261
									
								
								models/workflow/workflow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								models/workflow/workflow.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,261 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Workflow is a struct that represents a workflow
 | 
			
		||||
* it defines the native workflow
 | 
			
		||||
 */
 | 
			
		||||
type Workflow struct {
 | 
			
		||||
	utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	resources.ResourceSet
 | 
			
		||||
	Graph          *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
 | 
			
		||||
	ScheduleActive bool         `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
 | 
			
		||||
	// Schedule       *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
 | 
			
		||||
	Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow     // AbstractWorkflow contains the basic fields of a workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Deps struct {
 | 
			
		||||
	Source string
 | 
			
		||||
	Dest   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) IsDependancy(id string) []Deps {
 | 
			
		||||
	dependancyOfIDs := []Deps{}
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		if _, ok := w.Graph.Items[link.Destination.ID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		source := w.Graph.Items[link.Destination.ID].Processing
 | 
			
		||||
		if id == link.Source.ID && source != nil {
 | 
			
		||||
			dependancyOfIDs = append(dependancyOfIDs, Deps{Source: source.GetName(), Dest: link.Destination.ID})
 | 
			
		||||
		}
 | 
			
		||||
		sourceWF := w.Graph.Items[link.Destination.ID].Workflow
 | 
			
		||||
		if id == link.Source.ID && sourceWF != nil {
 | 
			
		||||
			dependancyOfIDs = append(dependancyOfIDs, Deps{Source: sourceWF.GetName(), Dest: link.Destination.ID})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return dependancyOfIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetDependencies(id string) (dependencies []Deps) {
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		if _, ok := w.Graph.Items[link.Source.ID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		source := w.Graph.Items[link.Source.ID].Processing
 | 
			
		||||
		if id == link.Destination.ID && source != nil {
 | 
			
		||||
			dependencies = append(dependencies, Deps{Source: source.GetName(), Dest: link.Source.ID})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) {
 | 
			
		||||
	for _, item := range w.Graph.Items {
 | 
			
		||||
		if f(item) {
 | 
			
		||||
			list_datas = append(list_datas, item)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetPricedItem(
 | 
			
		||||
	f func(item graph.GraphItem) bool, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) map[string]pricing.PricedItemITF {
 | 
			
		||||
	list_datas := map[string]pricing.PricedItemITF{}
 | 
			
		||||
	for _, item := range w.Graph.Items {
 | 
			
		||||
		if f(item) {
 | 
			
		||||
			dt, res := item.GetResource()
 | 
			
		||||
			ord := res.ConvertToPricedResource(dt, request)
 | 
			
		||||
			list_datas[res.GetID()] = ord
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return list_datas
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Related struct {
 | 
			
		||||
	Node  resources.ResourceInterface
 | 
			
		||||
	Links []graph.GraphLink
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) map[string]Related {
 | 
			
		||||
	related := map[string]Related{}
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		nodeID := link.Destination.ID
 | 
			
		||||
		var node resources.ResourceInterface
 | 
			
		||||
		if g(w.Graph.Items[link.Source.ID]) {
 | 
			
		||||
			item := w.Graph.Items[link.Source.ID]
 | 
			
		||||
			_, node = item.GetResource()
 | 
			
		||||
		}
 | 
			
		||||
		if node == nil && g(w.Graph.Items[link.Destination.ID]) { // if the source is not a storage, we consider that the destination is the storage
 | 
			
		||||
			nodeID = link.Source.ID
 | 
			
		||||
			item := w.Graph.Items[link.Destination.ID] // and the processing is the source
 | 
			
		||||
			_, node = item.GetResource()               // we are looking for the storage as destination
 | 
			
		||||
		}
 | 
			
		||||
		if processingID == nodeID && node != nil { // if the storage is linked to the processing
 | 
			
		||||
			if _, ok := related[processingID]; !ok {
 | 
			
		||||
				related[processingID] = Related{}
 | 
			
		||||
			}
 | 
			
		||||
			rel := related[node.GetID()]
 | 
			
		||||
			rel.Node = node
 | 
			
		||||
			rel.Links = append(rel.Links, link)
 | 
			
		||||
			related[processingID] = rel
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return related
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	isAuthorized := false
 | 
			
		||||
	if len(ao.Shared) > 0 {
 | 
			
		||||
		for _, shared := range ao.Shared {
 | 
			
		||||
			shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(shared)
 | 
			
		||||
			if code != 200 || shared == nil {
 | 
			
		||||
				isAuthorized = false
 | 
			
		||||
			} else {
 | 
			
		||||
				isAuthorized = shared.VerifyAuth(request)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ao.AbstractObject.VerifyAuth(request) || isAuthorized
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* CheckBooking is a function that checks the booking of the workflow on peers (even ourselves)
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
 | 
			
		||||
	// check if
 | 
			
		||||
	if wfa.Graph == nil { // no graph no booking
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resources.ComputeResource{}).GetAccessor(&tools.APIRequest{Caller: caller})
 | 
			
		||||
	for _, link := range wfa.Graph.Links {
 | 
			
		||||
		if ok, compute_id := link.IsComputeLink(*wfa.Graph); ok { // check if the link is a link between a compute and a resource
 | 
			
		||||
			compute, code, _ := accessor.LoadOne(compute_id)
 | 
			
		||||
			if code != 200 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// CHECK BOOKING ON PEER, compute could be a remote one
 | 
			
		||||
			peerID := compute.(*resources.ComputeResource).CreatorID
 | 
			
		||||
			if peerID == "" {
 | 
			
		||||
				return false, errors.New("no peer id")
 | 
			
		||||
			} // no peer id no booking, we need to know where to book
 | 
			
		||||
			_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, compute_id, tools.BOOKING, tools.GET, nil, caller)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
 | 
			
		||||
	priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
 | 
			
		||||
	ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
 | 
			
		||||
		func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
 | 
			
		||||
		}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
			s := started.Add(time.Duration(duration))
 | 
			
		||||
			return &s
 | 
			
		||||
		})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request,
 | 
			
		||||
		wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			return start, 0
 | 
			
		||||
		}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
			return end
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
 | 
			
		||||
		if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request,
 | 
			
		||||
			f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
				nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
 | 
			
		||||
					if f(i) {
 | 
			
		||||
						_, r = i.GetResource()
 | 
			
		||||
					}
 | 
			
		||||
					return r
 | 
			
		||||
				}, request)
 | 
			
		||||
				return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
 | 
			
		||||
			}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
				s := started.Add(time.Duration(duration))
 | 
			
		||||
				return &s
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
			return 0, priceds, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	longest := common.GetPlannerLongestTime(end, priceds, request)
 | 
			
		||||
	if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
 | 
			
		||||
		func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
 | 
			
		||||
			longest := float64(-1)
 | 
			
		||||
			r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
 | 
			
		||||
			if code != 200 || err != nil {
 | 
			
		||||
				return start, longest
 | 
			
		||||
			}
 | 
			
		||||
			if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
 | 
			
		||||
				return start, longest
 | 
			
		||||
			} else if neoLongest > longest {
 | 
			
		||||
				longest = neoLongest
 | 
			
		||||
			}
 | 
			
		||||
			return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest
 | 
			
		||||
		}, func(start time.Time, longest float64) *time.Time {
 | 
			
		||||
			s := start.Add(time.Duration(longest) * time.Second)
 | 
			
		||||
			return &s
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return longest, priceds, wf, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func plan[T resources.ResourceInterface](
 | 
			
		||||
	dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest,
 | 
			
		||||
	f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) {
 | 
			
		||||
	resources := []T{}
 | 
			
		||||
	for _, item := range wf.GetGraphItems(f) {
 | 
			
		||||
		if priceds[dt] == nil {
 | 
			
		||||
			priceds[dt] = map[string]pricing.PricedItemITF{}
 | 
			
		||||
		}
 | 
			
		||||
		dt, realItem := item.GetResource()
 | 
			
		||||
		if realItem == nil {
 | 
			
		||||
			return resources, priceds, errors.New("could not load the processing resource")
 | 
			
		||||
		}
 | 
			
		||||
		priced := realItem.ConvertToPricedResource(dt, request)
 | 
			
		||||
		// Should be commented once the Pricing selection feature has been implemented, related to the commit d35ad440fa77763ec7f49ab34a85e47e75581b61
 | 
			
		||||
		// if priced.SelectPricing() == nil {
 | 
			
		||||
		// 	return resources, priceds, errors.New("no pricings are selected... can't proceed")
 | 
			
		||||
		// }
 | 
			
		||||
		started, duration := start(realItem, priced)
 | 
			
		||||
		priced.SetLocationStart(started)
 | 
			
		||||
		if duration >= 0 {
 | 
			
		||||
			if e := end(started, duration); e != nil {
 | 
			
		||||
				priced.SetLocationEnd(*e)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if e := end(started, priced.GetExplicitDurationInS()); e != nil {
 | 
			
		||||
			priced.SetLocationEnd(*e)
 | 
			
		||||
		}
 | 
			
		||||
		resources = append(resources, realItem.(T))
 | 
			
		||||
		priceds[dt][item.ID] = priced
 | 
			
		||||
	}
 | 
			
		||||
	return resources, priceds, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								models/workflow/workflow_history_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								models/workflow/workflow_history_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WorkflowHistory struct{ Workflow }
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowHistory) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessorHistory(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (r *WorkflowHistory) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Workspace is a struct that represents a workspace
 | 
			
		||||
type workflowHistoryMongoAccessor struct {
 | 
			
		||||
	workflowMongoAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the workspaceMongoAccessor
 | 
			
		||||
func NewHistory() *workflowHistoryMongoAccessor {
 | 
			
		||||
	return &workflowHistoryMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *workflowHistoryMongoAccessor) MapFromWorkflow(w *Workflow) *WorkflowHistory {
 | 
			
		||||
	wh := &WorkflowHistory{Workflow: *w}
 | 
			
		||||
	wh.GenerateID()
 | 
			
		||||
	return wh
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										224
									
								
								models/workflow/workflow_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								models/workflow/workflow_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,224 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type workflowMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor    // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	computeResourceAccessor   utils.Accessor
 | 
			
		||||
	collaborativeAreaAccessor utils.Accessor
 | 
			
		||||
	workspaceAccessor         utils.Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return new(tools.WORKFLOW_HISTORY, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return new(tools.WORKFLOW, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the workflowMongoAccessor
 | 
			
		||||
func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return &workflowMongoAccessor{
 | 
			
		||||
		computeResourceAccessor:   (&resources.ComputeResource{}).GetAccessor(request),
 | 
			
		||||
		collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request),
 | 
			
		||||
		workspaceAccessor:         (&workspace.Workspace{}).GetAccessor(request),
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteOne deletes a workflow from the database, delete depending executions and bookings
 | 
			
		||||
func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := utils.GenericDeleteOne(id, a)
 | 
			
		||||
	if res != nil && code == 200 {
 | 
			
		||||
		a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow
 | 
			
		||||
		a.share(res.(*Workflow), true, a.GetCaller())
 | 
			
		||||
	}
 | 
			
		||||
	return a.verifyResource(res), code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* share is a function that shares a workflow to the peers if the workflow is shared
 | 
			
		||||
 */
 | 
			
		||||
func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *tools.HTTPCaller) {
 | 
			
		||||
	if realData == nil || realData.Shared == nil || len(realData.Shared) == 0 || caller == nil || caller.Disabled { // no shared no sharing
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, sharedID := range realData.Shared { // loop through the shared ids
 | 
			
		||||
		res, code, _ := a.collaborativeAreaAccessor.LoadOne(sharedID)
 | 
			
		||||
		if code != 200 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		paccess := &peer.Peer{}
 | 
			
		||||
		for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
 | 
			
		||||
			paccess.UUID = p
 | 
			
		||||
			if ok, _ := paccess.IsMySelf(); ok { // if the peer is the current peer, never share because it will create a loop
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if delete { // if the workflow is deleted, share the deletion	orderResourceAccessor     utils.Accessor
 | 
			
		||||
 | 
			
		||||
				history := NewHistory()
 | 
			
		||||
				history.StoreOne(history.MapFromWorkflow(res.(*Workflow)))
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE,
 | 
			
		||||
					map[string]interface{}{}, caller)
 | 
			
		||||
			} else { // if the workflow is updated, share the update
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT,
 | 
			
		||||
					res.Serialize(res), caller)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			a.Logger.Error().Msg(err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateOne updates a workflow in the database
 | 
			
		||||
func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// avoid the update if the schedule is the same
 | 
			
		||||
	set = a.verifyResource(set)
 | 
			
		||||
	if set.(*Workflow).Graph != nil && set.(*Workflow).Graph.Partial {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
 | 
			
		||||
	}
 | 
			
		||||
	res, code, err := utils.GenericUpdateOne(set, id, a, &Workflow{})
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	workflow := res.(*Workflow)
 | 
			
		||||
	a.execute(workflow, false, true)        // update the workspace for the workflow
 | 
			
		||||
	a.share(workflow, false, a.GetCaller()) // share the update to the peers
 | 
			
		||||
	return res, code, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreOne stores a workflow in the database
 | 
			
		||||
func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data = a.verifyResource(data)
 | 
			
		||||
	d := data.(*Workflow)
 | 
			
		||||
	if d.Graph != nil && d.Graph.Partial {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
 | 
			
		||||
	}
 | 
			
		||||
	res, code, err := utils.GenericStoreOne(d, a)
 | 
			
		||||
	if err != nil || code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	workflow := res.(*Workflow)
 | 
			
		||||
 | 
			
		||||
	a.share(workflow, false, a.GetCaller()) // share the creation to the peers
 | 
			
		||||
	a.execute(workflow, false, true)        // store the workspace for the workflow
 | 
			
		||||
	return res, code, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CopyOne copies a workflow in the database
 | 
			
		||||
func (a *workflowMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	wf := data.(*Workflow)
 | 
			
		||||
	for _, item := range wf.Graph.Items {
 | 
			
		||||
		_, obj := item.GetResource()
 | 
			
		||||
		if obj != nil {
 | 
			
		||||
			obj.ClearEnv()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// execute is a function that executes a workflow
 | 
			
		||||
// it stores the workflow resources in a specific workspace to never have a conflict in UI and logic
 | 
			
		||||
func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active bool) {
 | 
			
		||||
	filters := &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by standard workspace name attached to a workflow
 | 
			
		||||
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	resource, _, err := a.workspaceAccessor.Search(filters, "", workflow.IsDraft)
 | 
			
		||||
	if delete { // if delete is set to true, delete the workspace
 | 
			
		||||
		for _, r := range resource {
 | 
			
		||||
			a.workspaceAccessor.DeleteOne(r.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil && len(resource) > 0 { // if the workspace already exists, update it
 | 
			
		||||
		a.workspaceAccessor.UpdateOne(&workspace.Workspace{
 | 
			
		||||
			Active: active,
 | 
			
		||||
			ResourceSet: resources.ResourceSet{
 | 
			
		||||
				Datas:       workflow.Datas,
 | 
			
		||||
				Processings: workflow.Processings,
 | 
			
		||||
				Storages:    workflow.Storages,
 | 
			
		||||
				Workflows:   workflow.Workflows,
 | 
			
		||||
				Computes:    workflow.Computes,
 | 
			
		||||
			},
 | 
			
		||||
		}, resource[0].GetID())
 | 
			
		||||
	} else { // if the workspace does not exist, create it
 | 
			
		||||
		a.workspaceAccessor.StoreOne(&workspace.Workspace{
 | 
			
		||||
			Active:         active,
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: workflow.Name + "_workspace"},
 | 
			
		||||
			ResourceSet: resources.ResourceSet{
 | 
			
		||||
				Datas:       workflow.Datas,
 | 
			
		||||
				Processings: workflow.Processings,
 | 
			
		||||
				Storages:    workflow.Storages,
 | 
			
		||||
				Workflows:   workflow.Workflows,
 | 
			
		||||
				Computes:    workflow.Computes,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		w := d.(*Workflow)
 | 
			
		||||
		a.execute(w, false, true) // if no workspace is attached to the workflow, create it
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return a.verifyResource(d) }, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObject {
 | 
			
		||||
	wf := obj.(*Workflow)
 | 
			
		||||
	if wf.Graph == nil {
 | 
			
		||||
		return wf
 | 
			
		||||
	}
 | 
			
		||||
	for _, item := range wf.Graph.Items {
 | 
			
		||||
		t, resource := item.GetResource()
 | 
			
		||||
		if resource == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var access utils.Accessor
 | 
			
		||||
		if t == tools.COMPUTE_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.ComputeResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ComputeResource{} })
 | 
			
		||||
		} else if t == tools.PROCESSING_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.ProcessingResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ProcessingResource{} })
 | 
			
		||||
		} else if t == tools.STORAGE_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.StorageResource](t, a.GetRequest(), func() utils.DBObject { return &resources.StorageResource{} })
 | 
			
		||||
		} else if t == tools.WORKFLOW_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.WorkflowResource](t, a.GetRequest(), func() utils.DBObject { return &resources.WorkflowResource{} })
 | 
			
		||||
		} else if t == tools.DATA_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.DataResource](t, a.GetRequest(), func() utils.DBObject { return &resources.DataResource{} })
 | 
			
		||||
		} else {
 | 
			
		||||
			wf.Graph.Clear(resource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		if error := utils.VerifyAccess(access, resource.GetID()); error != nil {
 | 
			
		||||
			wf.Graph.Clear(resource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return wf
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								models/workflow/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								models/workflow/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStoreOneWorkflow(t *testing.T) {
 | 
			
		||||
	w := Workflow{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wma := NewAccessor(nil)
 | 
			
		||||
	id, _, _ := wma.StoreOne(&w)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneWorkflow(t *testing.T) {
 | 
			
		||||
	w := Workflow{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wma := NewAccessor(nil)
 | 
			
		||||
	new_w, _, _ := wma.StoreOne(&w)
 | 
			
		||||
	assert.Equal(t, w, new_w)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								models/workflow_execution/tests/workflow_scheduler_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								models/workflow_execution/tests/workflow_scheduler_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
package workflow_execution_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadOne(id string) (interface{}, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewScheduler_ValidInput(t *testing.T) {
 | 
			
		||||
	s := "2025-06-16T15:00:00"
 | 
			
		||||
	e := "2025-06-16T17:00:00"
 | 
			
		||||
	dur := 7200.0
 | 
			
		||||
	cronStr := "0 0 * * * *"
 | 
			
		||||
 | 
			
		||||
	sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, sched)
 | 
			
		||||
	assert.Equal(t, dur, sched.DurationS)
 | 
			
		||||
	assert.Equal(t, cronStr, sched.Cron)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewScheduler_InvalidStart(t *testing.T) {
 | 
			
		||||
	s := "invalid"
 | 
			
		||||
	e := "2025-06-16T17:00:00"
 | 
			
		||||
	dur := 7200.0
 | 
			
		||||
	cronStr := "0 0 * * * *"
 | 
			
		||||
 | 
			
		||||
	sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
 | 
			
		||||
	assert.Nil(t, sched)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewScheduler_InvalidEnd(t *testing.T) {
 | 
			
		||||
	s := "2025-06-16T15:00:00"
 | 
			
		||||
	e := "invalid"
 | 
			
		||||
	dur := 7200.0
 | 
			
		||||
	cronStr := "0 0 * * * *"
 | 
			
		||||
 | 
			
		||||
	sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
 | 
			
		||||
	assert.NotNil(t, sched)
 | 
			
		||||
	assert.Nil(t, sched.End)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetDates_NoCron(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	s := &workflow_execution.WorkflowSchedule{
 | 
			
		||||
		Start: start,
 | 
			
		||||
		End:   &end,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	schedule, err := s.GetDates()
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, schedule, 1)
 | 
			
		||||
	assert.Equal(t, start, schedule[0].Start)
 | 
			
		||||
	assert.Equal(t, end, *schedule[0].End)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetDates_InvalidCron(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	s := &workflow_execution.WorkflowSchedule{
 | 
			
		||||
		Start: start,
 | 
			
		||||
		End:   &end,
 | 
			
		||||
		Cron:  "bad cron",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := s.GetDates()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetDates_ValidCron(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(10 * time.Minute)
 | 
			
		||||
 | 
			
		||||
	s := &workflow_execution.WorkflowSchedule{
 | 
			
		||||
		Start:     start,
 | 
			
		||||
		End:       &end,
 | 
			
		||||
		DurationS: 60,
 | 
			
		||||
		Cron:      "0 */2 * * * *",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dates, err := s.GetDates()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, len(dates), 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetExecutions_Success(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	ws := &workflow_execution.WorkflowSchedule{
 | 
			
		||||
		UUID:  uuid.New().String(),
 | 
			
		||||
		Start: start,
 | 
			
		||||
		End:   &end,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wf := &workflow.Workflow{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			UUID: uuid.New().String(),
 | 
			
		||||
			Name: "TestWorkflow",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	execs, err := ws.GetExecutions(wf)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, len(execs), 0)
 | 
			
		||||
	assert.Equal(t, wf.UUID, execs[0].WorkflowID)
 | 
			
		||||
	assert.Equal(t, ws.UUID, execs[0].ExecutionsID)
 | 
			
		||||
	assert.Equal(t, enum.DRAFT, execs[0].State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSchedules_NoRequest(t *testing.T) {
 | 
			
		||||
	ws := &workflow_execution.WorkflowSchedule{}
 | 
			
		||||
 | 
			
		||||
	ws, wf, execs, err := ws.Schedules("someID", nil)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Nil(t, wf)
 | 
			
		||||
	assert.Len(t, execs, 0)
 | 
			
		||||
	assert.Equal(t, ws, ws)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Additional test stubs to be completed with gomock usage for:
 | 
			
		||||
// - CheckBooking
 | 
			
		||||
// - BookExecs
 | 
			
		||||
// - getBooking
 | 
			
		||||
// - Schedules (success path)
 | 
			
		||||
// - Planify mocking in CheckBooking
 | 
			
		||||
// - Peer interaction in BookExecs
 | 
			
		||||
// - Caller deep copy errors in getCallerCopy
 | 
			
		||||
// Will be continued...
 | 
			
		||||
							
								
								
									
										154
									
								
								models/workflow_execution/tests/workflow_test.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										154
									
								
								models/workflow_execution/tests/workflow_test.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,154 @@
 | 
			
		||||
package workflow_execution_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return nil, args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) Search(filters interface{}, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, search, isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreDraftDefault(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{}
 | 
			
		||||
	exec.StoreDraftDefault()
 | 
			
		||||
	assert.False(t, exec.IsDraft)
 | 
			
		||||
	assert.Equal(t, enum.SCHEDULED, exec.State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate_StateChange(t *testing.T) {
 | 
			
		||||
	existing := &workflow_execution.WorkflowExecution{State: enum.DRAFT}
 | 
			
		||||
	newExec := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
 | 
			
		||||
	canUpdate, updated := existing.CanUpdate(newExec)
 | 
			
		||||
	assert.True(t, canUpdate)
 | 
			
		||||
	assert.Equal(t, enum.SCHEDULED, updated.(*workflow_execution.WorkflowExecution).State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate_SameState_Draft(t *testing.T) {
 | 
			
		||||
	existing := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
 | 
			
		||||
	newExec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
 | 
			
		||||
	canUpdate, _ := existing.CanUpdate(newExec)
 | 
			
		||||
	assert.False(t, canUpdate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete_TrueIfDraft(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}}
 | 
			
		||||
	assert.True(t, exec.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete_FalseIfNotDraft(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: false}}
 | 
			
		||||
	assert.False(t, exec.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEquals_True(t *testing.T) {
 | 
			
		||||
	d := time.Now()
 | 
			
		||||
	exec1 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
 | 
			
		||||
	exec2 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
 | 
			
		||||
	assert.True(t, exec1.Equals(exec2))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEquals_False(t *testing.T) {
 | 
			
		||||
	exec1 := &workflow_execution.WorkflowExecution{ExecDate: time.Now(), WorkflowID: "abc"}
 | 
			
		||||
	exec2 := &workflow_execution.WorkflowExecution{ExecDate: time.Now().Add(time.Hour), WorkflowID: "def"}
 | 
			
		||||
	assert.False(t, exec1.Equals(exec2))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArgoStatusToState_Success(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{}
 | 
			
		||||
	exec.ArgoStatusToState("succeeded")
 | 
			
		||||
	assert.Equal(t, enum.SUCCESS, exec.State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArgoStatusToState_DefaultToFailure(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{}
 | 
			
		||||
	exec.ArgoStatusToState("unknown")
 | 
			
		||||
	assert.Equal(t, enum.FAILURE, exec.State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGenerateID_AssignsUUID(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{}
 | 
			
		||||
	exec.GenerateID()
 | 
			
		||||
	assert.NotEmpty(t, exec.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetName_ReturnsCorrectFormat(t *testing.T) {
 | 
			
		||||
	time := time.Now()
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{UUID: "abc"}, ExecDate: time}
 | 
			
		||||
	assert.Contains(t, exec.GetName(), "abc")
 | 
			
		||||
	assert.Contains(t, exec.GetName(), time.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAuth_AlwaysTrue(t *testing.T) {
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{}
 | 
			
		||||
	assert.True(t, exec.VerifyAuth(nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_RejectsZeroState(t *testing.T) {
 | 
			
		||||
	accessor := &workflow_execution.WorkflowExecutionMongoAccessor{}
 | 
			
		||||
	set := &workflow_execution.WorkflowExecution{State: 0}
 | 
			
		||||
	_, code, err := accessor.UpdateOne(set, "someID")
 | 
			
		||||
	assert.Equal(t, 400, code)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_DraftExpired_ShouldDelete(t *testing.T) {
 | 
			
		||||
	// Normally would mock time.Now and delete call; for now we test structure
 | 
			
		||||
	accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{
 | 
			
		||||
		ExecDate:       time.Now().Add(-2 * time.Minute),
 | 
			
		||||
		State:          enum.DRAFT,
 | 
			
		||||
		AbstractObject: utils.AbstractObject{UUID: "to-delete"},
 | 
			
		||||
	}
 | 
			
		||||
	_, _, _ = accessor.LoadOne(exec.GetID())
 | 
			
		||||
	// No panic = good enough placeholder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_ScheduledExpired_ShouldUpdateToForgotten(t *testing.T) {
 | 
			
		||||
	accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	exec := &workflow_execution.WorkflowExecution{
 | 
			
		||||
		ExecDate:       time.Now().Add(-2 * time.Minute),
 | 
			
		||||
		State:          enum.SCHEDULED,
 | 
			
		||||
		AbstractObject: utils.AbstractObject{UUID: "to-forget"},
 | 
			
		||||
	}
 | 
			
		||||
	_, _, _ = accessor.LoadOne(exec.GetID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_NotImplemented(t *testing.T) {
 | 
			
		||||
	accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	_, code, err := accessor.DeleteOne("someID")
 | 
			
		||||
	assert.Equal(t, 404, code)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_NotImplemented(t *testing.T) {
 | 
			
		||||
	accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	_, code, err := accessor.StoreOne(nil)
 | 
			
		||||
	assert.Equal(t, 404, code)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCopyOne_NotImplemented(t *testing.T) {
 | 
			
		||||
	accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	_, code, err := accessor.CopyOne(nil)
 | 
			
		||||
	assert.Equal(t, 404, code)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetExecFilters_BasicPattern(t *testing.T) {
 | 
			
		||||
	a := workflow_execution.NewAccessor(&tools.APIRequest{})
 | 
			
		||||
	filters := a.GetExecFilters("foo")
 | 
			
		||||
	assert.Contains(t, filters.Or["abstractobject.name"][0].Value, "foo")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										213
									
								
								models/workflow_execution/workflow_execution.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										213
									
								
								models/workflow_execution/workflow_execution.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,213 @@
 | 
			
		||||
package workflow_execution
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* WorkflowExecution is a struct that represents a list of workflow executions
 | 
			
		||||
* Warning: No user can write (del, post, put) a workflow execution, it is only used by the system
 | 
			
		||||
* workflows generate their own executions
 | 
			
		||||
 */
 | 
			
		||||
type WorkflowExecution struct {
 | 
			
		||||
	utils.AbstractObject                                // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	PeerBuyByGraph       map[string]map[string][]string `json:"peer_buy_by_graph,omitempty" bson:"peer_buy_by_graph,omitempty"`   // BookByResource is a map of the resource id and the list of the booking id
 | 
			
		||||
	PeerBookByGraph      map[string]map[string][]string `json:"peer_book_by_graph,omitempty" bson:"peer_book_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id
 | 
			
		||||
	ExecutionsID         string                         `json:"executions_id,omitempty" bson:"executions_id,omitempty"`
 | 
			
		||||
	ExecDate             time.Time                      `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required
 | 
			
		||||
	EndDate              *time.Time                     `json:"end_date,omitempty" bson:"end_date,omitempty"`                                 // EndDate is the end date of the workflow
 | 
			
		||||
	State                enum.BookingStatus             `json:"state" bson:"state" default:"0"`                                               // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow
 | 
			
		||||
	WorkflowID           string                         `json:"workflow_id" bson:"workflow_id,omitempty"`                                     // WorkflowID is the ID of the workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowExecution) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = false // TODO: TEMPORARY
 | 
			
		||||
	r.State = enum.SCHEDULED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowExecution) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if r.State != set.(*WorkflowExecution).State {
 | 
			
		||||
		return true, &WorkflowExecution{State: set.(*WorkflowExecution).State} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return !r.IsDraft, set // only draft buying can be updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowExecution) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *WorkflowExecution) Equals(we *WorkflowExecution) bool {
 | 
			
		||||
	return wfa.ExecDate.Equal(we.ExecDate) && wfa.WorkflowID == we.WorkflowID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowExecution) PurgeDraft(request *tools.APIRequest) error {
 | 
			
		||||
	if ws.EndDate == nil {
 | 
			
		||||
		// if no end... then Book like a savage
 | 
			
		||||
		e := ws.ExecDate.Add(time.Hour)
 | 
			
		||||
		ws.EndDate = &e
 | 
			
		||||
	}
 | 
			
		||||
	accessor := ws.GetAccessor(request)
 | 
			
		||||
	res, code, err := accessor.Search(&dbs.Filters{
 | 
			
		||||
		And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date
 | 
			
		||||
			"state":       {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
 | 
			
		||||
			"workflow_id": {{Operator: dbs.EQUAL.String(), Value: ws.WorkflowID}},
 | 
			
		||||
			"execution_date": {
 | 
			
		||||
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*ws.EndDate)},
 | 
			
		||||
				{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(ws.ExecDate)},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", ws.IsDraft)
 | 
			
		||||
	if code != 200 || err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range res {
 | 
			
		||||
		accessor.DeleteOne(r.GetID())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tool to transform the argo status to a state
 | 
			
		||||
func (wfa *WorkflowExecution) ArgoStatusToState(status string) *WorkflowExecution {
 | 
			
		||||
	status = strings.ToLower(status)
 | 
			
		||||
	switch status {
 | 
			
		||||
	case "succeeded": // Succeeded
 | 
			
		||||
		wfa.State = enum.SUCCESS
 | 
			
		||||
	case "pending": // Pending
 | 
			
		||||
		wfa.State = enum.SCHEDULED
 | 
			
		||||
	case "running": // Running
 | 
			
		||||
		wfa.State = enum.STARTED
 | 
			
		||||
	default: // Failed
 | 
			
		||||
		wfa.State = enum.FAILURE
 | 
			
		||||
	}
 | 
			
		||||
	return wfa
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowExecution) GenerateID() {
 | 
			
		||||
	if r.UUID == "" {
 | 
			
		||||
		r.UUID = uuid.New().String()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) GetName() string {
 | 
			
		||||
	return d.UUID + "_" + d.ExecDate.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
booking is an activity reserved for not a long time investment.
 | 
			
		||||
... purchase is dependant of a one time buying.
 | 
			
		||||
use of a datacenter or storage can't be buy for permanent access.
 | 
			
		||||
*/
 | 
			
		||||
func (d *WorkflowExecution) Buy(bs pricing.BillingStrategy, executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource {
 | 
			
		||||
	purchases := d.buyEach(bs, executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])
 | 
			
		||||
	purchases = append(purchases, d.buyEach(bs, executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...)
 | 
			
		||||
	return purchases
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID string, wfID string, dt tools.DataType, priceds map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource {
 | 
			
		||||
	items := []*purchase_resource.PurchaseResource{}
 | 
			
		||||
	for itemID, priced := range priceds {
 | 
			
		||||
		if !priced.IsPurchasable() || bs != pricing.BILL_ONCE { // buy only that must be buy
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBuyByGraph == nil {
 | 
			
		||||
			d.PeerBuyByGraph = map[string]map[string][]string{}
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBuyByGraph[priced.GetCreatorID()] == nil {
 | 
			
		||||
			d.PeerBuyByGraph[priced.GetCreatorID()] = map[string][]string{}
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBuyByGraph[priced.GetCreatorID()][itemID] == nil {
 | 
			
		||||
			d.PeerBuyByGraph[priced.GetCreatorID()][itemID] = []string{}
 | 
			
		||||
		}
 | 
			
		||||
		start := d.ExecDate
 | 
			
		||||
		if s := priced.GetLocationStart(); s != nil {
 | 
			
		||||
			start = *s
 | 
			
		||||
		}
 | 
			
		||||
		end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
 | 
			
		||||
		bookingItem := &purchase_resource.PurchaseResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{
 | 
			
		||||
				UUID: uuid.New().String(),
 | 
			
		||||
				Name: d.GetName() + "_" + executionsID + "_" + wfID,
 | 
			
		||||
			},
 | 
			
		||||
			PricedItem:   priced,
 | 
			
		||||
			ExecutionsID: executionsID,
 | 
			
		||||
			DestPeerID:   priced.GetCreatorID(),
 | 
			
		||||
			ResourceID:   priced.GetID(),
 | 
			
		||||
			ResourceType: dt,
 | 
			
		||||
			EndDate:      &end,
 | 
			
		||||
		}
 | 
			
		||||
		items = append(items, bookingItem)
 | 
			
		||||
		d.PeerBuyByGraph[priced.GetCreatorID()][itemID] = append(
 | 
			
		||||
			d.PeerBuyByGraph[priced.GetCreatorID()][itemID], bookingItem.GetID())
 | 
			
		||||
	}
 | 
			
		||||
	return items
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) Book(executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*booking.Booking {
 | 
			
		||||
	booking := d.bookEach(executionsID, wfID, tools.STORAGE_RESOURCE, priceds[tools.STORAGE_RESOURCE])
 | 
			
		||||
	booking = append(booking, d.bookEach(executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])...)
 | 
			
		||||
	booking = append(booking, d.bookEach(executionsID, wfID, tools.COMPUTE_RESOURCE, priceds[tools.COMPUTE_RESOURCE])...)
 | 
			
		||||
	booking = append(booking, d.bookEach(executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...)
 | 
			
		||||
	return booking
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.DataType, priceds map[string]pricing.PricedItemITF) []*booking.Booking {
 | 
			
		||||
	items := []*booking.Booking{}
 | 
			
		||||
	for itemID, priced := range priceds {
 | 
			
		||||
		if !priced.IsBooked() { // book only that must be booked
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBookByGraph == nil {
 | 
			
		||||
			d.PeerBookByGraph = map[string]map[string][]string{}
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBookByGraph[priced.GetCreatorID()] == nil {
 | 
			
		||||
			d.PeerBookByGraph[priced.GetCreatorID()] = map[string][]string{}
 | 
			
		||||
		}
 | 
			
		||||
		if d.PeerBookByGraph[priced.GetCreatorID()][itemID] == nil {
 | 
			
		||||
			d.PeerBookByGraph[priced.GetCreatorID()][itemID] = []string{}
 | 
			
		||||
		}
 | 
			
		||||
		start := d.ExecDate
 | 
			
		||||
		if s := priced.GetLocationStart(); s != nil {
 | 
			
		||||
			start = *s
 | 
			
		||||
		}
 | 
			
		||||
		end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
 | 
			
		||||
		bookingItem := &booking.Booking{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{
 | 
			
		||||
				UUID: uuid.New().String(),
 | 
			
		||||
				Name: d.GetName() + "_" + executionsID + "_" + wfID,
 | 
			
		||||
			},
 | 
			
		||||
			PricedItem:        priced,
 | 
			
		||||
			ExecutionsID:      executionsID,
 | 
			
		||||
			State:             enum.SCHEDULED,
 | 
			
		||||
			ResourceID:        priced.GetID(),
 | 
			
		||||
			ResourceType:      dt,
 | 
			
		||||
			DestPeerID:        priced.GetCreatorID(),
 | 
			
		||||
			WorkflowID:        wfID,
 | 
			
		||||
			ExecutionID:       d.GetID(),
 | 
			
		||||
			ExpectedStartDate: start,
 | 
			
		||||
			ExpectedEndDate:   &end,
 | 
			
		||||
		}
 | 
			
		||||
		items = append(items, bookingItem)
 | 
			
		||||
		d.PeerBookByGraph[priced.GetCreatorID()][itemID] = append(
 | 
			
		||||
			d.PeerBookByGraph[priced.GetCreatorID()][itemID], bookingItem.GetID())
 | 
			
		||||
	}
 | 
			
		||||
	return items
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								models/workflow_execution/workflow_execution_mongo_accessor.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										107
									
								
								models/workflow_execution/workflow_execution_mongo_accessor.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
package workflow_execution
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WorkflowExecutionMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
	shallow bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newShallowAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
 | 
			
		||||
	return &WorkflowExecutionMongoAccessor{
 | 
			
		||||
		shallow: true,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.WORKFLOW_EXECUTION,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
 | 
			
		||||
	return &WorkflowExecutionMongoAccessor{
 | 
			
		||||
		shallow: false,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.WORKFLOW_EXECUTION,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *WorkflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return nil, 404, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *WorkflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	if set.(*WorkflowExecution).State == 0 {
 | 
			
		||||
		return nil, 400, errors.New("state is required")
 | 
			
		||||
	}
 | 
			
		||||
	realSet := WorkflowExecution{State: set.(*WorkflowExecution).State}
 | 
			
		||||
	return utils.GenericUpdateOne(&realSet, id, wfa, &WorkflowExecution{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *WorkflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return nil, 404, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *WorkflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return nil, 404, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WorkflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*WorkflowExecution).State == enum.DRAFT && !a.shallow && now.UTC().After(d.(*WorkflowExecution).ExecDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), newShallowAccessor(a.Request))
 | 
			
		||||
			return nil, 404, errors.New("not found")
 | 
			
		||||
		}
 | 
			
		||||
		if d.(*WorkflowExecution).State == enum.SCHEDULED && !a.shallow && now.UTC().After(d.(*WorkflowExecution).ExecDate) {
 | 
			
		||||
			d.(*WorkflowExecution).State = enum.FORGOTTEN
 | 
			
		||||
			utils.GenericRawUpdateOne(d, id, newShallowAccessor(a.Request))
 | 
			
		||||
		}
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WorkflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WorkflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*WorkflowExecution](filters, search, a.GetExecFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WorkflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*WorkflowExecution).State == enum.DRAFT && now.UTC().After(d.(*WorkflowExecution).ExecDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), newShallowAccessor(a.Request))
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if d.(*WorkflowExecution).State == enum.SCHEDULED && now.UTC().After(d.(*WorkflowExecution).ExecDate) {
 | 
			
		||||
			d.(*WorkflowExecution).State = enum.FORGOTTEN
 | 
			
		||||
			utils.GenericRawUpdateOne(d, d.GetID(), newShallowAccessor(a.Request))
 | 
			
		||||
			return d
 | 
			
		||||
		}
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WorkflowExecutionMongoAccessor) GetExecFilters(search string) *dbs.Filters {
 | 
			
		||||
	return &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
 | 
			
		||||
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}},
 | 
			
		||||
		}}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										321
									
								
								models/workflow_execution/workflow_scheduler.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										321
									
								
								models/workflow_execution/workflow_scheduler.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,321 @@
 | 
			
		||||
package workflow_execution
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/bill"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/robfig/cron"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* WorkflowSchedule is a struct that contains the scheduling information of a workflow
 | 
			
		||||
* It contains the mode of the schedule (Task or Service), the name of the schedule, the start and end time of the schedule and the cron expression
 | 
			
		||||
 */
 | 
			
		||||
// it's a flying object only use in a session time. It's not stored in the database
 | 
			
		||||
type WorkflowSchedule struct {
 | 
			
		||||
	UUID              string               `json:"id" validate:"required"`                // ExecutionsID is the list of the executions id of the workflow
 | 
			
		||||
	Workflow          *workflow.Workflow   `json:"workflow,omitempty"`                    // Workflow is the workflow dependancy of the schedule
 | 
			
		||||
	WorkflowExecution []*WorkflowExecution `json:"workflow_executions,omitempty"`         // WorkflowExecution is the list of executions of the workflow
 | 
			
		||||
	Message           string               `json:"message,omitempty"`                     // Message is the message of the schedule
 | 
			
		||||
	Warning           string               `json:"warning,omitempty"`                     // Warning is the warning message of the schedule
 | 
			
		||||
	Start             time.Time            `json:"start" validate:"required,ltfield=End"` // Start is the start time of the schedule, is required and must be less than the End time
 | 
			
		||||
	End               *time.Time           `json:"end,omitempty"`                         // End is the end time of the schedule, is required and must be greater than the Start time
 | 
			
		||||
	DurationS         float64              `json:"duration_s" default:"-1"`               // End is the end time of the schedule
 | 
			
		||||
	Cron              string               `json:"cron,omitempty"`                        // here the cron format : ss mm hh dd MM dw task
 | 
			
		||||
 | 
			
		||||
	SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule {
 | 
			
		||||
	s, err := time.Parse("2006-01-02T15:04:05", start)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	ws := &WorkflowSchedule{
 | 
			
		||||
		UUID:      uuid.New().String(),
 | 
			
		||||
		Start:     s,
 | 
			
		||||
		DurationS: durationInS,
 | 
			
		||||
		Cron:      cron,
 | 
			
		||||
	}
 | 
			
		||||
	e, err := time.Parse("2006-01-02T15:04:05", end)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		ws.End = &e
 | 
			
		||||
	}
 | 
			
		||||
	return ws
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest) (bool, *workflow.Workflow, []*WorkflowExecution, []*purchase_resource.PurchaseResource, []*booking.Booking, error) {
 | 
			
		||||
	if request.Caller == nil && request.Caller.URLS == nil && request.Caller.URLS[tools.BOOKING] == nil || request.Caller.URLS[tools.BOOKING][tools.GET] == "" {
 | 
			
		||||
		return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("no caller defined")
 | 
			
		||||
	}
 | 
			
		||||
	access := workflow.NewAccessor(request)
 | 
			
		||||
	res, code, err := access.LoadOne(wfID)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("could not load the workflow with id: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	wf := res.(*workflow.Workflow)
 | 
			
		||||
	longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err
 | 
			
		||||
	}
 | 
			
		||||
	ws.DurationS = longest
 | 
			
		||||
	ws.Message = "We estimate that the workflow will start at " + ws.Start.String() + " and last " + fmt.Sprintf("%v", ws.DurationS) + " seconds."
 | 
			
		||||
	if ws.End != nil && ws.Start.Add(time.Duration(longest)*time.Second).After(*ws.End) {
 | 
			
		||||
		ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n"
 | 
			
		||||
	}
 | 
			
		||||
	execs, err := ws.GetExecutions(wf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err
 | 
			
		||||
	}
 | 
			
		||||
	purchased := []*purchase_resource.PurchaseResource{}
 | 
			
		||||
	bookings := []*booking.Booking{}
 | 
			
		||||
	for _, exec := range execs {
 | 
			
		||||
		purchased = append(purchased, exec.Buy(ws.SelectedBillingStrategy, ws.UUID, wfID, priceds)...)
 | 
			
		||||
		bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errCh := make(chan error, len(bookings))
 | 
			
		||||
	var m sync.Mutex
 | 
			
		||||
 | 
			
		||||
	for _, b := range bookings {
 | 
			
		||||
		go getBooking(b, request, errCh, &m)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(bookings); i++ {
 | 
			
		||||
		if err := <-errCh; err != nil {
 | 
			
		||||
			return false, wf, execs, purchased, bookings, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, wf, execs, purchased, bookings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowSchedule) GenerateOrder(purchases []*purchase_resource.PurchaseResource, bookings []*booking.Booking, request *tools.APIRequest) error {
 | 
			
		||||
	newOrder := &order.Order{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			Name:    "order_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
 | 
			
		||||
			IsDraft: true,
 | 
			
		||||
		},
 | 
			
		||||
		ExecutionsID: ws.UUID,
 | 
			
		||||
		Purchases:    purchases,
 | 
			
		||||
		Bookings:     bookings,
 | 
			
		||||
		Status:       enum.PENDING,
 | 
			
		||||
	}
 | 
			
		||||
	if res, _, err := order.NewAccessor(request).StoreOne(newOrder); err == nil {
 | 
			
		||||
		if _, err := bill.DraftFirstBill(res.(*order.Order), request); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	} else {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBooking(b *booking.Booking, request *tools.APIRequest, errCh chan error, m *sync.Mutex) {
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	c, err := getCallerCopy(request, errCh)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errCh <- err
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	m.Unlock()
 | 
			
		||||
 | 
			
		||||
	meth := c.URLS[tools.BOOKING][tools.GET]
 | 
			
		||||
	meth = strings.ReplaceAll(meth, ":id", b.ResourceID)
 | 
			
		||||
	meth = strings.ReplaceAll(meth, ":start_date", b.ExpectedStartDate.Format("2006-01-02T15:04:05"))
 | 
			
		||||
	meth = strings.ReplaceAll(meth, ":end_date", b.ExpectedEndDate.Format("2006-01-02T15:04:05"))
 | 
			
		||||
	c.URLS[tools.BOOKING][tools.GET] = meth
 | 
			
		||||
	_, err = (&peer.Peer{}).LaunchPeerExecution(b.DestPeerID, b.ResourceID, tools.BOOKING, tools.GET, nil, &c)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errCh <- err
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errCh <- nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCallerCopy(request *tools.APIRequest, errCh chan error) (tools.HTTPCaller, error) {
 | 
			
		||||
	var c tools.HTTPCaller
 | 
			
		||||
	err := request.Caller.DeepCopy(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errCh <- err
 | 
			
		||||
		return tools.HTTPCaller{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	c.URLS = request.Caller.URLS
 | 
			
		||||
	return c, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*WorkflowSchedule, *workflow.Workflow, []*WorkflowExecution, error) {
 | 
			
		||||
	if request == nil {
 | 
			
		||||
		return ws, nil, []*WorkflowExecution{}, errors.New("no request found")
 | 
			
		||||
	}
 | 
			
		||||
	c := request.Caller
 | 
			
		||||
	if c == nil || c.URLS == nil || c.URLS[tools.BOOKING] == nil {
 | 
			
		||||
		return ws, nil, []*WorkflowExecution{}, errors.New("no caller defined")
 | 
			
		||||
	}
 | 
			
		||||
	methods := c.URLS[tools.BOOKING]
 | 
			
		||||
	if _, ok := methods[tools.GET]; !ok {
 | 
			
		||||
		return ws, nil, []*WorkflowExecution{}, errors.New("no path found")
 | 
			
		||||
	}
 | 
			
		||||
	ok, wf, executions, purchases, bookings, err := ws.GetBuyAndBook(wfID, request)
 | 
			
		||||
	ws.WorkflowExecution = executions
 | 
			
		||||
	if !ok || err != nil {
 | 
			
		||||
		return ws, nil, executions, errors.New("could not book the workflow : " + fmt.Sprintf("%v", err))
 | 
			
		||||
	}
 | 
			
		||||
	ws.Workflow = wf
 | 
			
		||||
 | 
			
		||||
	var errCh = make(chan error, len(bookings))
 | 
			
		||||
	var m sync.Mutex
 | 
			
		||||
 | 
			
		||||
	for _, purchase := range purchases {
 | 
			
		||||
		go ws.CallDatacenter(purchase, purchase.DestPeerID, tools.PURCHASE_RESOURCE, request, errCh, &m)
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < len(purchases); i++ {
 | 
			
		||||
		if err := <-errCh; err != nil {
 | 
			
		||||
			return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errCh = make(chan error, len(bookings))
 | 
			
		||||
 | 
			
		||||
	for _, booking := range bookings {
 | 
			
		||||
		go ws.CallDatacenter(booking, booking.DestPeerID, tools.BOOKING, request, errCh, &m)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(bookings); i++ {
 | 
			
		||||
		if err := <-errCh; err != nil {
 | 
			
		||||
			return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ws.GenerateOrder(purchases, bookings, request); err != nil {
 | 
			
		||||
		return ws, wf, executions, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Schedules")
 | 
			
		||||
	for _, exec := range executions {
 | 
			
		||||
		err := exec.PurgeDraft(request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ws, nil, []*WorkflowExecution{}, errors.New("purge draft" + fmt.Sprintf("%v", err))
 | 
			
		||||
		}
 | 
			
		||||
		exec.StoreDraftDefault()
 | 
			
		||||
		utils.GenericStoreOne(exec, NewAccessor(request))
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("Schedules")
 | 
			
		||||
	return ws, wf, executions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowSchedule) CallDatacenter(purchase utils.DBObject, destPeerID string, dt tools.DataType, request *tools.APIRequest, errCh chan error, m *sync.Mutex) {
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	c, err := getCallerCopy(request, errCh)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errCh <- err
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	m.Unlock()
 | 
			
		||||
	if res, err := (&peer.Peer{}).LaunchPeerExecution(destPeerID, "", dt, tools.POST, purchase.Serialize(purchase), &c); err != nil {
 | 
			
		||||
		errCh <- err
 | 
			
		||||
		return
 | 
			
		||||
	} else {
 | 
			
		||||
		data := res["data"].(map[string]interface{})
 | 
			
		||||
		purchase.SetID(fmt.Sprintf("%v", data["id"]))
 | 
			
		||||
	}
 | 
			
		||||
	errCh <- nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
BOOKING IMPLIED TIME, not of subscription but of execution
 | 
			
		||||
so is processing time execution time applied on computes
 | 
			
		||||
data can improve the processing time
 | 
			
		||||
time should implied a security time border (10sec) if not from the same executions
 | 
			
		||||
VERIFY THAT WE HANDLE DIFFERENCE BETWEEN LOCATION TIME && BOOKING
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* getExecutions is a function that returns the executions of a workflow
 | 
			
		||||
* it returns an array of workflow_execution.WorkflowExecution
 | 
			
		||||
 */
 | 
			
		||||
func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow) ([]*WorkflowExecution, error) {
 | 
			
		||||
	workflows_executions := []*WorkflowExecution{}
 | 
			
		||||
	dates, err := ws.GetDates()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return workflows_executions, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, date := range dates {
 | 
			
		||||
		obj := &WorkflowExecution{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{
 | 
			
		||||
				UUID: uuid.New().String(),                                 // set the uuid of the execution
 | 
			
		||||
				Name: workflow.Name + "_execution_" + date.Start.String(), // set the name of the execution
 | 
			
		||||
			},
 | 
			
		||||
			ExecutionsID: ws.UUID,
 | 
			
		||||
			ExecDate:     date.Start,       // set the execution date
 | 
			
		||||
			EndDate:      date.End,         // set the end date
 | 
			
		||||
			State:        enum.DRAFT,       // set the state to 1 (scheduled)
 | 
			
		||||
			WorkflowID:   workflow.GetID(), // set the workflow id dependancy of the execution
 | 
			
		||||
		}
 | 
			
		||||
		workflows_executions = append(workflows_executions, obj)
 | 
			
		||||
	}
 | 
			
		||||
	return workflows_executions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *WorkflowSchedule) GetDates() ([]Schedule, error) {
 | 
			
		||||
	schedule := []Schedule{}
 | 
			
		||||
	if len(ws.Cron) > 0 { // if cron is set then end date should be set
 | 
			
		||||
		if ws.End == nil {
 | 
			
		||||
			return schedule, errors.New("a cron task should have an end date")
 | 
			
		||||
		}
 | 
			
		||||
		if ws.DurationS <= 0 {
 | 
			
		||||
			ws.DurationS = ws.End.Sub(ws.Start).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		cronStr := strings.Split(ws.Cron, " ") // split the cron string to treat it
 | 
			
		||||
		if len(cronStr) < 6 {                  // if the cron string is less than 6 fields, return an error because format is : ss mm hh dd MM dw (6 fields)
 | 
			
		||||
			return schedule, errors.New("Bad cron message: (" + ws.Cron + "). Should be at least ss mm hh dd MM dw")
 | 
			
		||||
		}
 | 
			
		||||
		subCron := strings.Join(cronStr[:6], " ")
 | 
			
		||||
		// cron should be parsed as ss mm hh dd MM dw t (min 6 fields)
 | 
			
		||||
		specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) // create a new cron parser
 | 
			
		||||
		sched, err := specParser.Parse(subCron)                                                                // parse the cron string
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return schedule, errors.New("Bad cron message: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		// loop through the cron schedule to set the executions
 | 
			
		||||
		for s := sched.Next(ws.Start); !s.IsZero() && s.Before(*ws.End); s = sched.Next(s) {
 | 
			
		||||
			e := s.Add(time.Duration(ws.DurationS) * time.Second)
 | 
			
		||||
			schedule = append(schedule, Schedule{
 | 
			
		||||
				Start: s,
 | 
			
		||||
				End:   &e,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else { // if no cron, set the execution to the start date
 | 
			
		||||
		schedule = append(schedule, Schedule{
 | 
			
		||||
			Start: ws.Start,
 | 
			
		||||
			End:   ws.End,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return schedule, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Schedule struct {
 | 
			
		||||
	Start time.Time
 | 
			
		||||
	End   *time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* TODO : LARGEST GRAIN PLANIFYING THE WORKFLOW WHEN OPTION IS SET
 | 
			
		||||
* SET PROTECTION BORDER TIME
 | 
			
		||||
 */
 | 
			
		||||
							
								
								
									
										215
									
								
								models/workspace/tests/workspace_mongo_accessor_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								models/workspace/tests/workspace_mongo_accessor_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
// File: workspace_accessor_test.go
 | 
			
		||||
 | 
			
		||||
package workspace_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockWorkspaceAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	workspace.Workspace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(set, id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockWorkspaceAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, search, isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "test_ws"}}
 | 
			
		||||
	mockAcc.On("StoreOne", ws).Return(ws, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.StoreOne(ws)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, ws, res)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_Conflict(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "duplicate"}}
 | 
			
		||||
	mockAcc.On("StoreOne", ws).Return(nil, 409, errors.New("a workspace with the same name already exists"))
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.StoreOne(ws)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 409, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "123", IsDraft: false}}
 | 
			
		||||
	mockAcc.On("UpdateOne", ws, "123").Return(ws, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.UpdateOne(ws, "123")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, ws, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_Error(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "999"}}
 | 
			
		||||
	err := errors.New("update failed")
 | 
			
		||||
	mockAcc.On("UpdateOne", ws, "999").Return(nil, 500, err)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.UpdateOne(ws, "999")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 500, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "321"}}
 | 
			
		||||
	mockAcc.On("DeleteOne", "321").Return(ws, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.DeleteOne("321")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, ws, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_NotFound(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	err := errors.New("not found")
 | 
			
		||||
	mockAcc.On("DeleteOne", "notfound").Return(nil, 404, err)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.DeleteOne("notfound")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 404, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "loadid"}}
 | 
			
		||||
	mockAcc.On("LoadOne", "loadid").Return(ws, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.LoadOne("loadid")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, ws, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_Error(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	err := errors.New("db error")
 | 
			
		||||
	mockAcc.On("LoadOne", "badid").Return(nil, 500, err)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.LoadOne("badid")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 500, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadAll_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "all1"}}
 | 
			
		||||
	mockAcc.On("LoadAll", true).Return([]utils.ShallowDBObject{ws}, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.LoadAll(true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Len(t, res, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadAll_Empty(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	mockAcc.On("LoadAll", false).Return([]utils.ShallowDBObject{}, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.LoadAll(false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Empty(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSearch_Success(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	filters := &dbs.Filters{}
 | 
			
		||||
	mockAcc.On("Search", filters, "keyword", true).Return([]utils.ShallowDBObject{}, 200, nil)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.Search(filters, "keyword", true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.NotNil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSearch_Error(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	filters := &dbs.Filters{}
 | 
			
		||||
	err := errors.New("search failed")
 | 
			
		||||
	mockAcc.On("Search", filters, "fail", false).Return(nil, 500, err)
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.Search(filters, "fail", false)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 500, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Additional edge test cases
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_InvalidType(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	mockAcc.On("StoreOne", mock.Anything).Return(nil, 400, errors.New("invalid type"))
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.StoreOne(&utils.AbstractObject{})
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 400, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_NilData(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	mockAcc.On("UpdateOne", nil, "id").Return(nil, 400, errors.New("nil data"))
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.UpdateOne(nil, "id")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 400, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_NilID(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockWorkspaceAccessor)
 | 
			
		||||
	mockAcc.On("DeleteOne", "").Return(nil, 400, errors.New("missing ID"))
 | 
			
		||||
 | 
			
		||||
	res, code, err := mockAcc.DeleteOne("")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 400, code)
 | 
			
		||||
	assert.Nil(t, res)
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user