حكاية عن كيفية صنع آلة الزمن لقاعدة البيانات وكتابة استغلال

يوم جيد ، هبر.

هل تساءلت يومًا عن كيفية تغيير الوقت داخل قاعدة البيانات؟ سهل؟ حسنًا ، في بعض الحالات ، نعم ، الأمر سهل - الأمر لينكس هو التاريخ والنقطة في القبعة. وإذا كنت بحاجة إلى تغيير الوقت فقط داخل مثيل واحد من قاعدة البيانات إذا كان هناك العديد منهم على الخادم؟ ولعملية قاعدة بيانات واحدة؟ و؟ آه ، هذا كل شيء ، يا صديقي ، هذه هي النقطة كلها.سيقول شخص ما أن هذا هو سور آخر ، لا علاقة له بالواقع ، والذي يتم وضعه بشكل دوري على حبري. ولكن لا ، المهمة حقيقية تمامًا وتمليها ضرورة الإنتاج - اختبار الكود. على الرغم من أنني أوافق ، يمكن أن تكون حالة الاختبار غريبة تمامًا - تحقق من كيفية عمل الرمز في تاريخ معين في المستقبل. في هذه المقالة ، سأفحص بالتفصيل كيف تم حل هذه المهمة ، وفي الوقت نفسه التقط القليل من عملية تنظيم الاختبار وتقف ديف على قاعدة أوراكل. قبل القراءة الطويلة ، كن مرتاحًا واطلب قطة.

خلفية


لنبدأ بمقدمة قصيرة لتوضيح سبب ضرورة ذلك. كما تم الإعلان عنه بالفعل ، نكتب اختبارات عند تنفيذ التعديلات في قاعدة البيانات. تم تطوير النظام الذي يتم بموجبه إجراء هذه الاختبارات في البداية (أو ربما قبل البدء بقليل) من الصفر ، لذلك يكون كل منطق الأعمال داخل قاعدة البيانات ومكتوبًا في شكل إجراءات مخزنة بلغة pl / sql. ونعم ، يجلب لنا الألم والمعاناة. لكن هذا هو الإرث ، وعليك أن تتعايش معه. في الكود والنموذج الجدولي ، من الممكن تحديد كيفية تطور المعلمات داخل النظام بمرور الوقت ، وبعبارة أخرى ، قم بتعيين النشاط من التاريخ والتاريخ الذي يمكن تطبيقه فيه. ما يجب قطعه - التغيير الأخير في معدل ضريبة القيمة المضافة هو مثال حي على ذلك. وحتى يمكن التحقق من هذه التغييرات في النظام مسبقًا ،يجب نقل قاعدة بيانات بها مثل هذه التغييرات إلى تاريخ معين في المستقبل ، وستصبح معلمات الرمز في الجداول نشطة في "اللحظة الحالية". ونظرًا لخصائص النظام المدعوم ، لا يمكنك استخدام اختبارات وهمية من شأنها ببساطة تغيير القيمة المرجعة لتاريخ النظام الحالي باللغة التي تبدأ بها جلسة الاختبار.

لذا ، حددنا السبب ، ثم نحتاج إلى تحديد كيفية تحقيق الهدف. للقيام بذلك ، سأقدم عرضًا استعاديًا صغيرًا لخيارات بناء مقاعد الاختبار للمطورين وكيف بدأت كل جلسة اختبار.

العصر الحجري


ذات مرة ، عندما كانت الأشجار صغيرة ، وكانت الأطر المركزية كبيرة ، لم يكن هناك سوى خادم واحد للتطوير وكان يجري أيضًا اختبارات. ومن حيث المبدأ ، كان كل هذا كافياً للجميع ( 640 كيلو تكفي للجميع! )

السلبيات: لتنفيذ مهمة تغيير الوقت ، كان من الضروري إشراك العديد من الأقسام ذات الصلة - مسؤولي النظام (هل تغير الوقت على الخادم الفرعي من الجذر) ، مسؤولي DBMS (هل أعيد تشغيل قاعدة البيانات) ، المبرمجين ( كان من الضروري الإخطار بحدوث تغيير في الوقت ، لأن جزءًا من الكود توقف عن العمل ، على سبيل المثال ، توقفت الرموز المميزة على الويب التي تم إصدارها سابقًا لاستدعاء طرق واجهة برمجة التطبيقات (API) عن أن تكون صالحة وقد يكون هذا مفاجئًا) ، المختبرين (يختبرون أنفسهم) ... عندما تعيد الوقت إلى الوقت الحاضر تم تكرار كل شيء بترتيب عكسي.

العصور الوسطى


مع مرور الوقت ، زاد عدد المطورين في القسم وتوقف الخادم في وقت ما عن أن يكون كافيًا. يرجع ذلك بشكل أساسي إلى حقيقة أن المطورين المختلفين يريدون تغيير نفس حزمة pl / sql وإجراء اختبار لها (حتى بدون تغيير الوقت). سمع المزيد والمزيد من السخط: "إلى متى! يكفي تحمل هذا! مصانع للعمال ، أرض للفلاحين! كل مبرمج لديه قاعدة بيانات! " ومع ذلك ، إذا كان لديك عدد قليل من تيرابايت من قاعدة بيانات المنتجات ، و 50-100 مطور ، إذن بصراحة في هذا النموذج ، فإن المتطلبات ليست حقيقية للغاية. ولا يزال الجميع يرغبون في ألا يتخلف الاختبار وقاعدة التطوير كثيرًا عن المبيعات ، سواء من حيث الهيكل أو البيانات داخل الجداول. لذلك كان هناك خادم منفصل للاختبار ، دعنا نسميها مرحلة ما قبل الإنتاج. تم بناؤه من خادمين متطابقين ،حيث تم البيع لاستعادة قاعدة البيانات من باكز RMAN واستغرق الأمر حوالي 2-2.5 أيام. بعد الاسترداد ، قامت قاعدة البيانات بإخفاء هوية البيانات الشخصية وغيرها من البيانات المهمة وتم تطبيق الحمل من تطبيقات الاختبار على هذا الخادم (بالإضافة إلى عمل المبرمجين أنفسهم دائمًا مع الخادم الذي تمت استعادته مؤخرًا). تم ضمان العمل مع الخادم المطلوب باستخدام موارد ip للكتلة المدعومة من خلال corosync (جهاز تنظيم ضربات القلب). بينما يعمل الجميع مع الخادم النشط ، على العقدة الثانية ، يبدأ استرداد قاعدة البيانات مرة أخرى وبعد 2-3 أيام يغيرون الأماكن مرة أخرى.تم ضمان العمل مع الخادم المطلوب باستخدام موارد ip للكتلة المدعومة من خلال corosync (جهاز تنظيم ضربات القلب). بينما يعمل الجميع مع الخادم النشط ، على العقدة الثانية ، يبدأ استرداد قاعدة البيانات مرة أخرى وبعد 2-3 أيام يغيرون الأماكن مرة أخرى.تم ضمان العمل مع الخادم المطلوب باستخدام موارد ip للكتلة المدعومة من خلال corosync (جهاز تنظيم ضربات القلب). بينما يعمل الجميع مع الخادم النشط ، على العقدة الثانية ، يبدأ استرداد قاعدة البيانات مرة أخرى وبعد 2-3 أيام يغيرون الأماكن مرة أخرى.

من العيوب الواضحة: تحتاج إلى خادمين ومرتين من الموارد (القرص بشكل رئيسي) أكثر من المنتج.

الإيجابيات: تشغيل واختبار تغيير الوقت - يمكن إجراؤه على الخادم الثاني ، على الخادم الرئيسي في هذا الوقت يعيش المطورين ويباشرون أعمالهم. يحدث تغيير الخادم فقط عندما تكون قاعدة البيانات جاهزة ، ويكون وقت تعطل بيئة الاختبار ضئيلاً.

عصر التقدم العلمي والتكنولوجي


عندما انتقلنا إلى قاعدة بيانات 11g Release 2 ، قرأنا عن تقنية مثيرة للاهتمام توفرها Oracle تحت اسم CloneDB. خلاصة القول هي أن النسخ الاحتياطية لقاعدة بيانات المنتج (هناك نسخة مباشرة من ملفات بيانات المنتج) يتم تخزينها على خادم خاص ، والتي تنشر بعد ذلك هذه المجموعة من ملفات البيانات عبر DNFS (NFS المباشر) إلى أي عدد من الخوادم بشكل أساسي ، ولا تحتاج إلى وجود خادم على الخادم نفس الحجم من الأقراص ، لأنه يتم تنفيذ نهج النسخ عند الكتابة: تستخدم قاعدة البيانات مشاركة شبكة مع ملفات البيانات من خادم النسخ الاحتياطي لقراءة البيانات في الجداول ، وتكتب التغييرات على ملفات البيانات المحلية على خادم dev نفسه. بشكل دوري ، يتم "التقيد بالمواعيد النهائية" للخادم بحيث لا تنمو ملفات البيانات المحلية كثيرًا ولا ينتهي المكان. عند تحديث الخادم ، يتم أيضًا إلغاء تخصيص البيانات في الجداول ،في هذه الحالة ، تقع جميع تحديثات الجدول في ملفات البيانات المحلية وتتم قراءة هذه الجداول من الخادم المحلي ، وتتم قراءة جميع الجداول الأخرى عبر الشبكة.

السلبيات: لا يزال هناك خادمان (لضمان إجراء تحديثات سلسة مع الحد الأدنى من وقت التعطل للمستهلكين) ، ولكن الآن يتم تقليل حجم الأقراص بشكل كبير. لتخزين باكز على كرة nfs ، تحتاج إلى خادم آخر بحجم + - كمنتج ، ولكن يتم تقليل وقت تنفيذ التحديث نفسه (خاصة عند استخدام باكز إضافي). يؤدي التواصل مع كرة nfs إلى إبطاء عمليات قراءة IO بشكل ملحوظ. لاستخدام تقنية CloneDB ، يجب أن تكون القاعدة إصدار Enterprise ؛ في حالتنا ، كان علينا تنفيذ إجراء الترقية على أسس الاختبار في كل مرة. لحسن الحظ ، يتم استثناء قواعد بيانات الاختبار من سياسات ترخيص Oracle.

الإيجابيات: تستغرق عملية استعادة قاعدة من bakup أقل من يوم واحد (لا أتذكر الوقت بالضبط).

تغيير الوقت: لا تغييرات رئيسية. على الرغم من أنه بحلول هذا الوقت ، كانت البرامج النصية قد تم إجراؤها بالفعل لتغيير الوقت على الخادم وإعادة تشغيل قاعدة البيانات من أجل القيام بذلك دون جذب انتباه ترتيب المسؤولين.

عصر التاريخ الجديد


من أجل توفير مساحة أكبر على القرص وجعل قراءة البيانات في وضع عدم الاتصال ، قررنا تنفيذ إصدار CloneDB (مع الفلاش باك واللقطات) باستخدام نظام الملفات مع الضغط. خلال الاختبارات الأولية ، وقع الاختيار على ZFS ، على الرغم من عدم وجود دعم رسمي لها في نواة Linux (اقتباس من المقالة) للمقارنة ، نظرنا أيضًا في BTRFS (b-tree fs) ، الذي تروج له Oracle ، ولكن نسبة الضغط كانت أقل مع نفس استهلاك وحدة المعالجة المركزية وذاكرة الوصول العشوائي في الاختبارات. لتمكين دعم ZFS على RHEL5 ، تم بناء نواة خاصة به تعتمد على نواة المؤسسة غير القابلة للكسر ، وعلى محاور ونواة أحدث يمكنك ببساطة استخدام نواة UEK الجاهزة. يعتمد تنفيذ قاعدة الاختبار هذه أيضًا على آلية COW ، ولكن على مستوى لقطات نظام الملفات. يتم توفير جهازين للقرص إلى الخادم ، في أحدهما ، يتم إنشاء تجمع zfs ، حيث يتم من خلال RMAN إنشاء قاعدة بيانات احتياطية إضافية من البيع ، وبما أننا نستخدم الضغط ، فإن القسم يأخذ أقل من الإنتاج.
يتم تثبيت النظام على جهاز القرص الثاني والباقي ضروري للخادم وقاعدة البيانات نفسها للعمل ، على سبيل المثال ، أقسام التراجع ودرجة الحرارة. في أي وقت ، يمكنك إنشاء لقطة من تجمع zfs ، والتي تفتح بعد ذلك كقاعدة بيانات منفصلة. يستغرق إنشاء لقطة بضع ثوانٍ. انه سحر! ويمكن إمالة قواعد البيانات هذه من حيث المبدأ كثيرًا ، إذا كان الخادم فقط لديه ذاكرة وصول عشوائي كافية لجميع المثيلات وحجم تجمع zfs نفسه (لتخزين التغييرات في ملفات البيانات أثناء إزالة الشخصية وأثناء دورة حياة استنساخ قاعدة البيانات). الوقت الرئيسي لتحديث قاعدة الاختبار هو تشغيل إزالة تخصيص البيانات ، ولكنه يناسب أيضًا في 15-20 دقيقة. هناك تسارع كبير.

سلبيات: على الخادم لا يمكنك تغيير الوقت ببساطة عن طريق ترجمة وقت النظام ، لأنه بعد ذلك ستقع جميع مثيلات قاعدة البيانات التي تعمل على هذا الخادم في هذا الوقت دفعة واحدة. تم العثور على حل لهذه المشكلة وسيتم وصفه في القسم المناسب. بالمضي قدمًا ، سأقول إنه يسمح لك بتغيير الوقت داخل مثيل واحد فقط من قاعدة البيانات (نهج تغيير الوقت لكل مثيل) دون التأثير على الباقي على نفس الخادم. والوقت على الخادم نفسه لا يتغير سواء. هذا يلغي الحاجة إلى برنامج نصي جذري لتغيير الوقت على الخادم. أيضًا في هذه المرحلة ، يتم تنفيذ أتمتة تغيير الوقت للحالات عبر Jenkins CI ويتم منح المستخدمين (فرق التطوير التي تتحدث نسبيًا) الذين يمتلكون كشكهم حقوق الوظائف التي يمكنهم من خلالها تغيير الوقت بأنفسهم ، وتحديث المقصورة إلى الحالة الحالية مع المبيعات ، عمل لقطات واستعادة (استرجاع) القاعدة إلى اللقطة التي تم إنشاؤها سابقًا.

عصر التاريخ الحديث


مع ظهور Oracle 12c ، ظهرت تقنية جديدة - قواعد بيانات قابلة للتوصيل ، ونتيجة لذلك ، قواعد بيانات حاويات (cdb). باستخدام هذه التقنية ، ضمن مثيل مادي واحد ، يمكن إنشاء العديد من قواعد البيانات "الافتراضية" التي تشترك في مساحة ذاكرة مشتركة للمثيل. الإيجابيات: يمكنك حفظ الذاكرة للخادم (وزيادة الأداء العام لقاعدة البيانات الخاصة بنا ، لأن كل الذاكرة التي كانت مشغولة من قبل ، على سبيل المثال ، 5 حالات مختلفة ، يمكن مشاركتها لجميع حاويات pdb المنشورة داخل cdb ، وسوف يستخدمونها فقط عندما يحتاجون إليها حقًا ، وليس كما كان في المرحلة السابقة ، عندما "كل" حجبت الذاكرة المخصصة لها وعندما كان نشاط أحد الاستنساخ منخفضًا ، لم يتم استخدام الذاكرة بشكل فعال ، وبعبارة أخرى ، كانت خاملة).لا تزال ملفات البيانات من pdb مختلفة تكمن في تجمع zfs ، وعند نشر المستنسخات ، يستخدمون نفس آلية لقطة zfs. في هذه المرحلة ، اقتربنا بشكل كافٍ من القدرة على منح قاعدة بيانات المطورين لكل مطور تقريبًا. لا يتطلب تغيير الوقت في هذه المرحلة إعادة تشغيل قاعدة البيانات ويعمل بدقة عالية فقط لتلك العمليات التي تحتاج إلى تغيير الوقت ؛ لا يتأثر جميع المستخدمين الآخرين الذين يعملون مع قاعدة البيانات هذه بأي شكل من الأشكال.

ناقص: لا يمكنك استخدام نهج تغيير الوقت لكل مثيل من المرحلة السابقة ، لأن لدينا مثيل واحد الآن. ومع ذلك ، تم العثور على حل لهذه الحالة. وكان هذا على وجه التحديد بمثابة الدافع لكتابة هذا المقال. واستشرافا للمستقبل ، سأقول أنه تغيير الوقت لكل نهج عملية ، أي في كل عملية قاعدة بيانات ، يمكنك تعيين وقتك الفريد بشكل عام.

في هذه الحالة ، تحدد جلسة اختبار نموذجية مباشرة بعد الاتصال بقاعدة البيانات الوقت المناسب في بداية عملها ، وتجري الاختبارات وتعيد الوقت في النهاية. يعد إرجاع الوقت ضروريًا لسبب واحد بسيط: لا تنتهي بعض عمليات قاعدة بيانات Oracle عند قطع عميل قاعدة البيانات عن الخادم ، فهذه عمليات خادم تسمى خوادم مشتركة ، والتي ، على عكس العمليات المخصصة ، يتم تشغيلها عند بدء تشغيل خادم قاعدة البيانات ويعيش إلى أجل غير مسمى (في الوضع المثالي صورة العالم). إذا تركت الوقت تغير في عملية الخادم هذه ، فإن اتصالًا آخر سيتم تقديمه في هذه العملية سيتلقى وقتًا خاطئًا.

في نظامنا ، يتم استخدام الخوادم المشتركة كثيرًا ، لأنه حتى 11 جرامًا ، لم يكن هناك أي حل مناسب لنظامنا لتحمل الحمل العالي (في 11 جرامًا ظهر DRCP - تجمع اتصال مقيم في قاعدة البيانات). وإليك السبب - يوجد حد فرعي في إجمالي عدد عمليات الخادم التي يمكن إنشاؤها في كل من الوضع المخصص والمشترك. يتم إنشاء العمليات المخصصة بشكل أبطأ مما يمكن لقاعدة البيانات إصدار عملية مشتركة جاهزة بالفعل من مجموعة العمليات المشتركة ، مما يعني أنه عندما تصل اتصالات جديدة باستمرار (خاصة إذا كانت العملية تقوم ببعض العمليات البطيئة الأخرى) ، فإن العدد الإجمالي للعمليات سيزداد. عند الوصول إلى الحد الأقصى للجلسات / العمليات ، تتوقف قاعدة البيانات عن خدمة الاتصالات الجديدة ويحدث الانهيار.أتاح لنا الانتقال إلى استخدام مجموعة من العمليات المشتركة تقليل عدد العمليات الجديدة على الخادم عند الاتصال.

هذا هو المكان الذي تكتمل فيه مراجعة تقنيات بناء قواعد بيانات الاختبار ، ويمكننا أخيرًا البدء في تنفيذ خوارزميات تغيير الوقت لقاعدة البيانات نفسها.

نهج المثال المزيف


كيف تغير الوقت داخل قاعدة البيانات؟

كان أول ما يتبادر إلى الذهن هو إنشاء نظام يحتوي على كل كود منطق الأعمال ، ووظيفته الخاصة ، والتي تتداخل مع وظائف اللغة التي تعمل مع الوقت (sysdate ، current_date ، وما إلى ذلك) ، وفي ظل ظروف معينة ، تبدأ في إعطاء قيم أخرى ، على سبيل المثال ، يمكنك تعيين القيم من خلال سياق الجلسة في بداية التشغيل التجريبي. لم ينجح ذلك ، لم تتداخل وظائف اللغة المضمنة مع وظائف المستخدم.

بعد ذلك ، تم اختبار أنظمة المحاكاة الافتراضية الخفيفة (Vserver ، OpenVZ) والحاويات عبر عامل الميناء. لا يعمل أيضًا ، فهم يستخدمون نفس النواة التي يستخدمها النظام المضيف ، مما يعني أنهم يستخدمون نفس قيم مؤقت النظام. السقوط مرة أخرى.

وهنا لا أخشى المجيء لإنقاذ هذه الكلمة ، وهو اختراع رائع لعالم Linux - إعادة تعريف / اعتراض الوظائف في مرحلة التحميل الديناميكي للكائنات المشتركة. ومن المعروف للعديد من الحيل مع LD_PRELOAD. في متغير البيئة LD_PRELOAD ، يمكنك تحديد المكتبة التي سيتم تحميلها قبل كل الآخرين التي تحتاجها العملية ، وإذا كانت هذه المكتبة تحتوي على أحرف تحمل نفس الاسم على سبيل المثال في libc القياسي ، والذي سيتم تحميله لاحقًا ، فسيظهر جدول استيراد الرمز للتطبيق إذا كانت الوظيفة يوفر وحدة استبدال لدينا. وهذا بالضبط ما تفعله مكتبة مشروع libfaketimeالتي بدأنا استخدامها من أجل بدء تشغيل قاعدة البيانات في وقت مختلف بشكل منفصل عن النظام. تفتقد المكتبة المكالمات التي تتعلق بالعمل مع مؤقت النظام والحصول على وقت وتاريخ النظام. للتحكم في مقدار تحركات الوقت بالنسبة لتاريخ الخادم الحالي أو من أي نقطة زمنية يجب أن يمر الوقت داخل العملية - يتم التحكم في كل شيء بواسطة متغيرات البيئة التي يجب ضبطها مع LD_PRELOAD. لتنفيذ تغيير الوقت ، قمنا بتنفيذ مهمة على خادم Jenkins ، الذي يدخل خادم قاعدة البيانات ويعيد تشغيل DBMS إما مع أو بدون متغيرات البيئة المعينة لـ libfaketime.

مثال خوارزمية لبدء قاعدة بيانات بوقت استبدال:

export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so
export FAKETIME="+1d"
export FAKETIME_NO_CACHE=1

$ORACLE_HOME/bin/sqlplus @/home/oracle/scripts/restart_db.sql

وإذا كنت تعتقد أن كل شيء يعمل على الفور ، فأنت مخطئ للغاية. لأنه ، كما اتضح ، يتحقق من تلك المكتبات التي يتم تحميلها في العملية عند بدء تشغيل DBMS. وفي التنبيه ، يبدأ في استياء التزوير الملاحظ ، في حين أن القاعدة لا تبدأ. الآن لا أتذكر بالضبط كيف أتخلص منه ، هناك بعض المعلمات التي يمكن أن تعطل تنفيذ عمليات التحقق من الصحة عند بدء التشغيل.

نهج وهمية لكل عملية


بقيت الفكرة العامة لتغيير الوقت فقط خلال عملية واحدة واحدة - استخدم libfaketime. نبدأ قاعدة البيانات بمكتبة محملة مسبقًا فيها ، ولكننا نضع إزاحة صفر عند بدء التشغيل ، والتي يتم نشرها بعد ذلك في جميع عمليات DBMS. ثم ، داخل جلسة الاختبار ، قم بتعيين متغير البيئة لهذه العملية فقط. Pff ، شيء تجاري.

ومع ذلك ، بالنسبة لأولئك الذين هم على دراية بلغة pl / sql ، فإن كل هذه الفكرة واضحة على الفور. لأن اللغة محدودة للغاية ومناسبة بشكل أساسي للمهام عالية المستوى. لا يمكن تنفيذ برمجة النظام هناك. على الرغم من وجود بعض العمليات ذات المستوى المنخفض (على سبيل المثال ، العمل مع شبكة ، العمل مع الملفات) في شكل حزم dbms / utl للنظام مثبتة مسبقًا. طوال الوقت الذي عملت فيه مع Oracle ، قمت بإجراء هندسة عكسية للحزم المثبتة مسبقًا عدة مرات ، وتم إخفاء رمز بعضها عن أعين الغرباء (يطلق عليهم ملفوفة). إذا تم منعك من مشاهدة شيء ما ، فإن الإغراء لمعرفة كيفية ترتيبه في الداخل يزداد فقط. ولكن في كثير من الأحيان ، حتى بعد الغلاف ، لا يوجد دائمًا شيء يمكن رؤيته ، لأن وظائف هذه الحزم يتم تنفيذها كواجهة c للمكتبات على القرص.
في المجموع ، اتصلنا بمرشح واحد للتنفيذ - التكنولوجيا بالإجراءات الخارجية .
يمكن للمكتبة المصممة بطريقة خاصة تصدير الطرق ، والتي يمكن بعد ذلك استدعاء قاعدة بيانات أوراكل عبر pl / sql. يبدو واعداً. مرة واحدة فقط قابلت هذا في دورات plsql المتقدمة ، لذلك تذكرت عن بعد كيفية طهيها. وهذا يعني أنه من الضروري قراءة الوثائق. قرأته - وعلى الفور اكتئب. لأن تحميل مثل هذه المكتبة المخصصة يذهب في عملية وكيل منفصلة من خلال مستمع قاعدة البيانات ، والتواصل مع هذا الوكيل يمر عبر dlink. لذا بكت فكرتنا لتعيين متغير بيئة داخل عملية قاعدة البيانات نفسها. وكل هذا يتم لأسباب أمنية.

صورة من الوثائق توضح كيفية عملها:



نوع مكتبة so / dll ليس مهمًا جدًا ، ولكن لسبب ما فإن الصورة مخصصة فقط لنظام التشغيل Windows.

ربما لاحظ شخص ما هنا فرصة محتملة أخرى. نعم ، نعم ، هذه جافا. تتيح لك Oracle كتابة رمز الإجراء المخزن ليس فقط في plsql ، ولكن أيضًا في java ، والتي يتم تصديرها بنفس الطريقة التي يتم بها تصدير طرق plsql. بشكل دوري ، قمت بذلك ، لذلك لا ينبغي أن تكون هناك مشكلة في ذلك. ولكن بعد ذلك تم إخفاء مأزق آخر. تعمل Java مع نسخة من البيئة ، وتسمح لك بالحصول فقط على متغيرات البيئة التي كانت عليها عملية JVM عند بدء التشغيل. يرث JVM المدمج متغيرات البيئة لعملية قاعدة البيانات ، ولكن هذا كل شيء. لقد رأيت نصائح على الإنترنت حول كيفية تغيير الخريطة للقراءة فقط من خلال التفكير ، ولكن ما هي النقطة ، لأنها لا تزال مجرد نسخة. أي أن المرأة تركت مرة أخرى بلا شيء.

ومع ذلك ، فإن Java ليست مجرد فروًا قيمًا. باستخدامه ، يمكنك إنتاج العمليات من داخل عملية قاعدة البيانات. على الرغم من أنه يجب حل جميع العمليات غير الآمنة بشكل منفصل من خلال آلية منح جافا ، والتي تتم باستخدام حزمة dbms_java. من داخل كود plsql ، يمكنك الحصول على معرف العملية الخاص بعملية الخادم الحالية التي يتم فيها تشغيل الكود ، وذلك باستخدام طريقة عرض النظام v $ session و v $ process. علاوة على ذلك ، يمكننا أن نفرز بعض عمليات الأطفال من جلستنا للقيام بشيء مع هذا pid. للبدء ، استنتجت ببساطة جميع متغيرات البيئة الموجودة داخل عملية قاعدة البيانات (لاختبار الفرضية)

#!/bin/sh

pid=$1

awk 'BEGIN {RS="\0"; ORS="\n"} $0' "/proc/$pid/environ"

استنتج جيدا ، ثم ماذا. لا يزال من المستحيل تغيير المتغيرات في ملف البيئة ، هذه هي البيانات التي تم نقلها إلى العملية عند بدئها ويتم قراءتها فقط.

لقد بحثت في الإنترنت على stackoverflow "كيفية تغيير متغير بيئة في عملية أخرى." كانت معظم الإجابات أنه كان مستحيلًا ، ولكن كان هناك إجابة واحدة وصفت هذه الفرصة بأنها اختراق مخترق وقذر. وكان هذا الجواب ألبرت أينشتاين gdb. يمكن أن يقوم المصحح بالربط بأي عملية مع معرفة معرف العملية وتنفيذ أي وظيفة / إجراء موجود فيها كرمز تم تصديره بشكل عام ، على سبيل المثال ، من بعض المكتبات. في libc ، هناك وظائف للعمل مع متغيرات البيئة ، ويتم تحميل libc في أي عملية لقاعدة بيانات Oracle (وعمليًا أي برنامج على لينكس).

هذه هي الطريقة التي يتم بها تعيين متغير البيئة في عملية خارجية (تحتاج إلى تسميته من الجذر بسبب ptrace المستخدمة):

#!/bin/sh

pid=$1
env_name=$2
env_val="$3"

out=`gdb -q -batch -ex "attach $pid" -ex 'call (int) setenv("'$env_name'", "'"$env_val"'", 1)' -ex "detach" 2>&1`


أيضا ، لرؤية متغيرات البيئة داخل عملية gdb مناسبة أيضًا. كما ذكرنا سابقًا ، فإن ملف البيئة من / proc / pid / يعرض فقط المتغيرات التي كانت موجودة في بداية العملية. وإذا كانت العملية قد خلقت شيئًا أثناء عملها ، فيمكن رؤية ذلك فقط من خلال المصحح:
#!/bin/sh

pid=$1
var_name=$2

var_value=`gdb -q -batch -ex "attach $pid" -ex 'call (char*) getenv("'$var_name'")' -ex 'detach' | egrep '^\$1 ='`

if [ "$var_value" == '$1 = 0x0' ]
then
  # variable empty or does not exist
  echo -n
else
  # gdb returns $1 = hex_value "string value"
  var_hex=`echo "$var_value" | awk '{print $3}'`
  var_value=`echo "$var_value" | sed -r -e 's/^\$1 = '$var_hex' //;s/^"//;s/"$//'`
  
  echo -n "$var_value"
fi


لذا ، فإن الحل موجود بالفعل في جيبنا - من خلال جافا نفرز عملية المصحح ، التي تذهب إلى العملية التي ولدت ذلك وتعيين متغير البيئة المرغوبة لها ثم ينتهي (يقوم مور بعمله - يمكن للمور المغادرة). ولكن كان هناك شعور بأنه نوع من العكاز. كنت أرغب في شيء أكثر أناقة. سيكون الأمر متشابهًا إلى حد ما لإجبار عملية قاعدة البيانات نفسها على تعيين متغيرات البيئة دون هجوم خارجي.

بيضة في بطة ، بطة في أرنب ...


ثم يأتي شخص ما إلى الإنقاذ ، نعم ، لقد خمنت ذلك بشكل صحيح ، مرة أخرى جافا ، وهي JNI (واجهة جافا الأصلية). يسمح لك JNI باستدعاء طرق C الأصلية داخل JVM. يتم إصدار الكود بطريقة خاصة في شكل كائن مشترك للمكتبة ، والذي يقوم JVM بتحميله ، بينما يتم تعيين الطرق الموجودة في المكتبة لطرق جافا داخل الفصل المعلن عنها مع المعدل الأصلي.

حسنًا ، حسنًا ، نحن نكتب فصلًا (في الواقع ، هذه مجرد قطعة عمل):

public class Posix {

    private static native int setenv(String key, String value, boolean overwrite);

    private static native String getenv(String key);
    
    public static void stub() 
    {
        
    }
}

بعد ذلك ، قم بتجميعه واحصل على الملف h الذي تم إنشاؤه من المكتبة المستقبلية:

#  
javac Posix.java

#   Posix.h        JNI
javah Posix

بعد استلام ملف الرأس ، نكتب النص لكل طريقة:

#include <stdlib.h>
#include "Posix.h"

JNIEXPORT jint JNICALL Java_Posix_setenv(JNIEnv *env, jclass cls, jstring key, jstring value, jboolean overwrite)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);

    int err = setenv(k, v, overwrite);

    (*env)->ReleaseStringUTFChars(env, key, k);
    (*env)->ReleaseStringUTFChars(env, value, v);

    return err;
}

JNIEXPORT jstring JNICALL Java_Posix_getenv(JNIEnv *env, jclass cls, jstring key)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = getenv(k);

    return (*env)->NewStringUTF(env, v);
}

وتجميع المكتبة

gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC Posix.c -shared -o libPosix.so -Wl,-soname -Wl,--no-whole-archive

strip libPosix.so

لكي تقوم Java بتحميل المكتبة الأصلية ، يجب أن يتم العثور عليها بواسطة النظام ld وفقًا لجميع قواعد Linux. بالإضافة إلى ذلك ، تحتوي Java على مجموعة من الخصائص التي تحتوي على المسارات التي تتم فيها عمليات البحث في المكتبة. أسهل طريقة للعمل داخل Oracle هي وضع مكتبتنا في $ ORACLE_HOME / lib.

وبعد إنشاء المكتبة ، نحتاج إلى تجميع الفصل داخل قاعدة البيانات ونشرها كحزمة plsql. هناك خياران لإنشاء فئات Java داخل قاعدة البيانات:

  • تحميل ملف ثنائي الطبقة عبر أداة loadjava
  • تجميع كود الفئة من المصدر باستخدام sqlplus

سنستخدم الطريقة الثانية ، على الرغم من أنها متساوية في الأساس. بالنسبة للحالة الأولى ، كان من الضروري كتابة كل كود الفئة على الفور في المرحلة 1 ، عندما تلقينا فئة كعب لملف h.

لإنشاء فئة في subd ، يتم استخدام بناء جملة خاص:

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "Posix" AS
...
...
/

عند إنشاء الفصل ، يجب نشره كطرق plsql ، وهنا مرة أخرى بناء الجملة الخاص:

procedure set_env(var_name varchar2, var_value varchar2)
is
language java name 'Posix.set_env(java.lang.String, java.lang.String)';

عند محاولة استدعاء طرق يحتمل أن تكون غير آمنة داخل Java ، يتم رفع التنفيذ الذي ينص على أنه لم يتم إصدار منحة جافا للمستخدم. يعد تحميل الطرق الأصلية عملية أخرى غير آمنة ، لأننا نقوم بإدخال كود غريب في عملية قاعدة البيانات مباشرة (نفس الاستغلال الذي تم الإعلان عنه في الرأس).

ولكن نظرًا لأن قاعدة البيانات هي اختبار ، فإننا نقدم منحة دون أي قلق من الاتصال بالأنظمة:

begin
dbms_java.grant_permission( 'SYSTEM', 'SYS:java.lang.RuntimePermission', 'loadLibrary.Posix', '');
commit;
end;
/

اسم المستخدم للنظام هو الذي قمت بتجميع كود جافا وحزمة المجمع plsql.
من المهم ملاحظة أنه عند تحميل مكتبة من خلال استدعاء System.loadLibrary ، فإننا نحذف البادئة lib والامتداد (كما هو موضح في الوثائق) ولا نمرر أي مسار للبحث فيه. هناك طريقة System.load مماثلة يمكنها فقط تحميل مكتبة باستخدام مسار مطلق.

ثم تنتظرنا مفاجأتان غير سارتين - لقد هبطت في حفرة الأرنب التالية في Oracle. عند إصدار منحة ، يحدث خطأ برسالة ضبابية إلى حد ما:

ORA-29532: Java call terminated by uncaught Java exception: java.lang.SecurityException: policy table update

يتم التعامل مع المشكلة على الإنترنت على Google ويؤدي إلى My Oracle Support (المعروف أيضًا باسم Metalink). لان وفقًا لقواعد أوراكل ، لا يُسمح بنشر مقالات من رابط معدني في مصادر مفتوحة ، سأذكر فقط رقم الوثيقة - 259471.1 (سيتمكن أولئك الذين لديهم حق الوصول من القراءة لأنفسهم).

جوهر المشكلة هو أن أوراكل لن تسمح لنا فقط بتحميل كود طرف ثالث مشبوه في عمليتنا. وهو أمر منطقي.

ولكن بما أن القاعدة هي اختبار ونحن واثقون من التعليمات البرمجية الخاصة بنا ، فإننا نسمح بالتنزيل دون مخاوف خاصة.
فوه ، المغامرات في كل مكان.

إنها حية ، حية


من خلال التنفس المتأخر ، قررت أن أحاول أن أتنفس الحياة في فرانكنشتاين.
نبدأ قاعدة البيانات باستخدام libfaketime الذي تم تحميله مسبقًا والإزاحة 0.
اتصل بقاعدة البيانات وقم بإجراء مكالمة إلى الرمز الذي يعرض ببساطة الوقت قبل وبعد تغيير متغير البيئة:

begin
dbms_output.enable(100000);
dbms_java.set_output(100000);
dbms_output.put_line('old time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
system.posix.set_env('FAKETIME','+1d');
dbms_output.put_line('new time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
end;
/


يعمل ، اللعنة! بصراحة ، كنت أتوقع المزيد من المفاجآت ، مثل أخطاء ORA-600. ومع ذلك ، كان التنبيه يحتوي على الرقم الكامل واستمر الرمز في العمل.
من المهم ملاحظة أنه إذا تم إجراء الاتصال بقاعدة البيانات على أنها مخصصة ، فبعد اكتمال الاتصال ، سيتم تدمير العملية ولن يكون هناك أي أثر. ولكن إذا استخدمنا اتصالات مشتركة ، ففي هذه الحالة يتم تخصيص عملية جاهزة من تجمع الخادم ، فإننا نغير الوقت فيها من خلال متغيرات البيئة ، وعندما يتم قطع الاتصال بها ، ستظل تتغير داخل العملية. وعندما تقع بعد ذلك جلسة عمل قاعدة بيانات أخرى في نفس عملية الخادم ، فإنها ستستغرق الوقت الخاطئ لمفاجأتها الكبيرة. لذلك ، في نهاية جلسة الاختبار ، من الأفضل دائمًا إعادة الوقت إلى صفر الإزاحة.

استنتاج


آمل أن تكون القصة مثيرة للاهتمام (وربما مفيدة لشخص ما).

تتوفر جميع رموز المصدر على Github .

وثائق libfaketime أيضًا .

كيف تقوم باختبار؟ وكيف تقوم بإنشاء قواعد بيانات مطورة واختبار في شركة؟

مكافأة لأولئك الذين يقرؤون حتى النهاية


All Articles